2 个版本
0.1.1 | 2023年10月31日 |
---|---|
0.1.0 | 2023年6月24日 |
#56 in 过程宏
1,265,548 每月下载量
在 2,289 个crate中(直接使用5个) 使用
27KB
421 行
更方便的 #[target_feature]
替代
为了从SIMD中获得良好的性能,SIMD代码路径上的所有内容都必须内联。在Rust中当前如何实现SIMD的情况下,一个函数要使用SIMD并且可内联,必须满足以下两个条件之一:(这包括SIMD intrinsic本身)
a) 整个程序必须使用相关的 -C target-cpu
或 -C target-feature
标志进行编译。
b) SIMD支持必须在运行时自动检测,SIMD代码路径上的每个函数都必须带有 #[target_feature]
标记。
两者都有其缺点。设置 target-cpu
或 target-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]
?
- 可以在
fn
、impl
和mod
上使用。 - 当用在函数上时,只会应用于该函数;它不会应用于任何嵌套函数、特性、模块等。
- 当用在
impl
上时,只会应用于该impl
内直接定义的所有函数。 - 当用在
mod
上时,只会应用于该mod
内直接定义的所有fn
和impl
。 - 不能用于使用
self
或Self
的方法;相反,应在定义该方法的相关impl
上使用。
许可证
许可协议为以下之一:
- Apache License,版本2.0,LICENSE-APACHE
- MIT许可证(LICENSE-MIT)
任选其一。
贡献
除非您明确声明,否则您有意提交并包含在本作品中的任何贡献,根据Apache-2.0许可证的定义,应双许可如上,不附加任何额外的条款或条件。
依赖关系
~255–700KB
~17K SLoC