2 个版本

0.1.1 2023年10月31日
0.1.0 2023年6月24日

#56 in 过程宏

Download history 260142/week @ 2024-04-23 244376/week @ 2024-04-30 244056/week @ 2024-05-07 279160/week @ 2024-05-14 275789/week @ 2024-05-21 297329/week @ 2024-05-28 280072/week @ 2024-06-04 268317/week @ 2024-06-11 250947/week @ 2024-06-18 261705/week @ 2024-06-25 239188/week @ 2024-07-02 272524/week @ 2024-07-09 308743/week @ 2024-07-16 301601/week @ 2024-07-23 305181/week @ 2024-07-30 299091/week @ 2024-08-06

1,265,548 每月下载量
2,289 个crate中(直接使用5个) 使用

MIT/Apache

27KB
421

更方便的 #[target_feature] 替代

为了从SIMD中获得良好的性能,SIMD代码路径上的所有内容都必须内联。在Rust中当前如何实现SIMD的情况下,一个函数要使用SIMD并且可内联,必须满足以下两个条件之一:(这包括SIMD intrinsic本身)

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

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

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

本库旨在使#[target_feature]的使用更加简便。

#[target_feature]存在的问题

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

  • 它只能在unsafe函数中使用,因此SIMD代码路径上的所有内容现在都必须是unsafe

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

  • 它不能用于非unsafe特征方法。

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

  • 它使得使用特征抽象给定SIMD指令集成为不可能。

    例如,假设您想通过以下方式使用特征抽象SIMD指令

    trait Backend {
        unsafe fn sum(input: &[u32]) -> u32;
    }
    
    struct AVX;
    # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    impl Backend for AVX {
        #[target_feature(enable = "avx")]
        unsafe fn sum(xs: &[u32]) -> u32 {
            // ...
            todo!();
        }
    }
    
    struct AVX2;
    # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    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指令集的具体功能标志,但这个函数是通用的,所以我们不能这样做!

这个库是如何使它变得更好的呢?

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

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

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

// No `unsafe` on the function itself!
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[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 curve25519_dalek_derive::unsafe_target_feature;

struct S;

# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[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 curve25519_dalek_derive::unsafe_target_feature_specialize;

# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[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() { /* ... */ }
}

# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
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上使用。
  • 当用在函数上时,只会应用于该函数;它不会应用于任何嵌套函数、特性、模块等。
  • 当用在impl上时,只会应用于该impl内直接定义的所有函数。
  • 当用在mod上时,只会应用于该mod内直接定义的所有fnimpl
  • 不能用于使用selfSelf的方法;相反,应在定义该方法的相关impl上使用。

许可证

许可协议为以下之一:

任选其一。

贡献

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

依赖关系

~255–700KB
~17K SLoC