#simd #target #intrinsics #replace #performance #run-time #target-feature

unsafe_target_feature

更便捷的 #[target_feature] 替代方案

2 个版本

0.1.1 2023年4月10日
0.1.0 2023年4月10日

295过程宏

Download history 1/week @ 2024-03-12 2/week @ 2024-03-26 25/week @ 2024-04-02 3/week @ 2024-04-16

每月111 次下载

MIT/Apache

26KB
421

更便捷的 #[target_feature] 替代方案

为了从SIMD中获取良好的性能,SIMD代码路径上的所有内容都必须内联。在Rust中当前实现的SIMD中,对于使用SIMD的函数来说,必须满足以下两个条件之一才能进行内联:(这包括SIMD内建函数本身)

a) 整个程序必须使用相关的 -C target-cpu-C target-feature 标志进行编译。

b) SIMD支持必须在运行时自动检测,并且SIMD代码路径上的每个函数都必须带有 #[target_feature] 属性。

两者都有其缺点。设置 target-cputarget-features 使得生成的二进制文件与旧CPU不兼容,而使用 #[target_feature] 则非常不便。

这个crate旨在使 #[target_feature] 的使用更顺畅。

#[target_feature] 的问题

当我们没有编译相关的 target-cpu/target-feature 标志时,SIMD代码路径上的所有内容都必须带有 #[target_feature] 属性。当所有SIMD代码都封装在一个函数中时,这并不是问题,但一旦开始构建更复杂的抽象,使用起来就会变得痛苦。

  • 它只能用于不安全函数,所以现在你的SIMD代码路径上的所有内容都必须是不安全

    从理论上讲这很好——这些函数需要在运行时存在相关的SIMD指令,所以在不进行检查的情况下调用它们显然是不安全的!但在实践中,这很少是您想要的。当您在SIMD代码上构建抽象时,通常希望假设在模块内部所有必要的SIMD指令都是可用的,并且您只想在第一次进入模块时在边界处进行检查。您不希望将不安全感染模块内的所有内容,因为您已经在模块的API边界处检查了此不变性。

  • 它不能用于非不安全的特质方法。

    如果您正在实现一个特质,例如例如std::ops::Add,那么除非原始特质也将其标记为不安全,否则您不能标记该方法为不安全,通常它不会。

  • 它使得无法通过特质抽象特定的SIMD指令集。

    例如,假设您想以以下方式使用特质抽象您使用的SIMD指令

    trait Backend {
        unsafe fn sum(input: &[u32]) -> u32;
    }
    
    struct AVX;
    impl Backend for AVX {
        #[target_feature(enable = "avx")]
        unsafe fn sum(xs: &[u32]) -> u32 {
            // ...
            todo!();
        }
    }
    
    struct AVX2;
    impl Backend for AVX2 {
        #[target_feature(enable = "avx2")]
        unsafe fn sum(xs: &[u32]) -> u32 {
            // ...
            todo!();
        }
    }
    
    // And now you want a have function which calls into that trait:
    unsafe fn do_calculations<B>(xs: &[u32]) -> u32 where B: Backend {
        let value = B::sum(xs);
        // ...do some more calculations here...
        value
    }
    

    这里有一个问题。这必须标记为#[target_feature],并且必须指定给定SIMD指令集的具体功能标志,但这个函数是泛型的,所以我们不能这样做!

这个crate如何改进这一点?

现在您可以使用#[target_feature]来标记安全函数

这个crate公开了一个#[unsafe_target_feature]宏,它的工作方式与#[target_feature]一样,但它将不安全从函数原型移动到宏名称中,并且可以用于安全函数。

// ERROR: `#[target_feature(..)]` can only be applied to `unsafe` functions
#[target_feature(enable = "avx2")]
fn func() {}
// It works, but must be `unsafe`
#[target_feature(enable = "avx2")]
unsafe fn func() {}
use unsafe_target_feature::unsafe_target_feature;

// No `unsafe` on the function itself!
#[unsafe_target_feature("avx2")]
fn func() {}

它还可以用于标记impl中的函数

struct S;

impl core::ops::Add for S {
    type Output = S;
    // ERROR: method `add` has an incompatible type for trait
    #[target_feature(enable = "avx2")]
    unsafe fn add(self, rhs: S) -> S {
        S
    }
}
use unsafe_target_feature::unsafe_target_feature;

struct S;

#[unsafe_target_feature("avx2")]
impl core::ops::Add for S {
    type Output = S;
    // No `unsafe` on the function itself!
    fn add(self, rhs: S) -> S {
        S
    }
}

您可以为每个目标功能生成模块的专用副本

use unsafe_target_feature::unsafe_target_feature_specialize;

#[unsafe_target_feature_specialize("sse2", "avx2", conditional("avx512ifma", nightly))]
mod simd {
    #[for_target_feature("sse2")]
    pub const CONSTANT: u32 = 1;

    #[for_target_feature("avx2")]
    pub const CONSTANT: u32 = 2;

    #[for_target_feature("avx512ifma")]
    pub const CONSTANT: u32 = 3;

    pub fn func() { /* ... */ }
}

fn entry_point() {
    #[cfg(nightly)]
    if std::is_x86_feature_detected!("avx512ifma") {
        return simd_avx512ifma::func();
    }

    if std::is_x86_feature_detected!("avx2") {
        return simd_avx2::func();
    }

    if std::is_x86_feature_detected!("sse2") {
        return simd_sse2::func();
    }

    unimplemented!();
}

如何使用#[unsafe_target_feature]

  • 可以用于fnimplmod
  • 当用于函数时,只会应用于该函数;它不会应用于任何嵌套函数、特质、mods等。
  • 当用于impl时,将只应用于该impl中直接定义的所有函数。
  • 当用于mod时,将仅适用于该mod内部直接定义的所有fnimpl
  • 不能用于使用selfSelf的方法;相反,应在定义该方法的impl中使用。

许可证

根据您的选择,许可协议为以下之一

贡献

除非您明确说明,否则您有意提交并包含在本作品中的任何贡献(根据 Apache-2.0 许可证定义),将根据上述协议双许可,不附加任何额外条款或条件。

依赖项

~285–740KB
~18K SLoC