2026年Rust中SIMD的现状

Lobsters Hottest 新闻

摘要

本文综述了2026年Rust中SIMD支持的现状,讨论了SIMD库的进展和实现细节。

<p><a href="https://lobste.rs/s/iotaty/state_simd_rust_2026">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/09/26 09:23

# Rust中SIMD的2026年现状 | Sergey "Shnatsel" Davidoff 来源:https://shnatsel.github.io/state-of-simd-rust-2026/ 自去年以来取得了巨大进展,其中一部分成果正是我做出的!在去年的调查(https://shnatsel.medium.com/the-state-of-simd-in-rust-in-2025-32c263e5f53d)之后,我开始为当时看起来最有前景的SIMD库做贡献。事情接踵而至,如今我已是Fearless SIMD的维护者之一。为避免利益冲突,我邀请了其他库(`std::simd`、`wide`、`pulp`、`macerator`)的作者审阅并提供了本文初稿的反馈。但我保留了编辑控制权,所有错误由我个人负责。今年的调查比上一次更为深入。所以请坐稳了,让我们*从头开始*! ## 什么是SIMD?为何使用SIMD?https://shnatsel.github.io/state-of-simd-rust-2026/#what-s-simd-why-simd 进行算术运算的硬件成本低廉,因此本世纪制造的任何CPU都拥有大量这样的硬件。但你仍然只有一个指令解码单元,且很难让它快速运行,因此算术硬件被严重低效利用。为突破指令解码瓶颈,你可以将一批数字一次性送给CPU,执行单一的算术操作(如加法)。因此得名:“单指令,多数据”,即SIMD。 你不再将两个数字相加,而是将两批或两“向量”的数字相加,所花时间与只做一次加法大致相同。在最新的x86芯片上,这些批次可达512位,因此理论上你可以为`f64`数学运算获得8倍加速,或为`u8`运算获得64倍加速。实际上,它可能运行得更慢(https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Downclocking)或更快(https://en.wikipedia.org/wiki/Instruction-level_parallelism)。 ## 指令集https://shnatsel.github.io/state-of-simd-rust-2026/#instruction-sets 历史上,SIMD指令是在CPU架构已经设计好之后添加的,因此SIMD是每个架构上的一个扩展,拥有自己的营销名称。ARM称其为“NEON”,所有64位ARM CPU都具备它。WebAssembly没有营销部门,因此他们只称其为“WebAssembly 128位打包SIMD扩展”。64位x86随一种称为“SSE2”的指令集一起发布,它提供了128位向量的基本指令,但*后来*他们在此基础上添加了一系列扩展,SSE 4.2添加了更多操作,AVX和AVX2添加了256位向量,AVX-512则添加了512位向量和更多操作。 上一段中的“后来”一词带来了问题。 ### 这个CPU有那个指令吗?https://shnatsel.github.io/state-of-simd-rust-2026/#does-this-cpu-have-that-instruction 如果你在x86_64 CPU上运行程序,该CPU不一定具备所有特定的SIMD扩展。因此,默认情况下编译器不允许使用SSE2之后的指令,因为那在所有x86_64 CPU上都无法运行。有两种解决方法。 如果你在一家公司工作,该公司只在自己的服务器或公共云上运行其二进制文件,你可以断言这些环境都足够新,至少具备10多年前引入的AVX2,并让程序在遇到不支持AVX2的硬件时崩溃或异常运行: ``` RUSTFLAGS='-C target-cpu=x86-64-v3' cargo build --release ``` 然而,如果你分发二进制文件供他人运行,这并非真正可行的选择。这时你可以使用一种称为**函数多版本化**的技术:针对不同的SIMD扩展多次编译同一函数,在程序实际运行时,检查CPU支持的功能,并据此选择相应版本。幸运的是,这个问题只存在于x86上。ARM在其64位CPU上强制要求NEON,并且之后并未添加太多有用的SIMD扩展(稍后详述)。WebAssembly则要求你编译两个不同的二进制文件,一个带SIMD,一个不带,并使用JavaScript来检查浏览器是否支持SIMD。 ## 如何使用SIMD?https://shnatsel.github.io/state-of-simd-rust-2026/#how-do-i-simd 利用SIMD有三种方式: 1. 自动向量化:`&[i32].sum()` 2. 可移植SIMD抽象:`i32x4 + i32x4` 3. 平台特定内部函数——稍等,我们需要一个更大的代码块: ``` #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),target_feature = "sse2"))] _mm_add_epi32(__m128i, __m128i) #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] vaddq_u32(int32x4_t, int32x4_t) ``` 让我们看看每种方式的含义以及各自编程模型的现状。 ## 自动向量化https://shnatsel.github.io/state-of-simd-rust-2026/#automatic-vectorization 只需编写普通的Rust代码,让编译器启发式方法来完成工作!如果你注意以编译器能可靠(大致)向量化的方式编写代码,你可以让它工作得相当好。这通常涉及遍历`&[i32].as_chunks()`而不是`&[i32]`,并通过基准测试或盯着汇编代码来验证是否成功。详情请参阅**你能相信编译器来优化你的代码吗?(https://matklad.github.io/2023/04/09/can-you-trust-a-compiler-to-optimize-your-code.html)** 这是最容易使用的选项,无需依赖,并且自动支持编译器支持的所有指令集,无论多么冷门。缺点是此方法不太可靠。你的函数越大越复杂,编译器无法向量化的可能性就越大。性能也可能因编译器版本或周围代码的更改而剧烈波动。浮点类型也需要特殊处理。 > **浮点数很奇怪。** 即使像以合理精度对浮点数组求和这样简单的事情也会变得出奇地复杂,参见**驯服浮点求和(https://orlp.net/blog/taming-float-sums/)**。此前,自动向量化不支持浮点类型,因为它会改变结果的精度(通常变得更好,但编译器不允许改变任何可观察结果)。这在Rust 1.98 (https://doc.rust-lang.org/stable/releases.html#version-1980-2026-08-20)中发生了变化,该版本稳定了诸如`algebraic_add()` (https://doc.rust-lang.org/stable/core/primitive.f32.html#method.algebraic_add)之类的代数运算,允许编译器改变可观察结果,就像一个不那么危险的`-ffast-math` (https://codingnest.com/files/Fun,%20Safe,%20Math%20Optimizations.pdf)。 在大多数情况下,你仍然需要重写代码以使用它们,才有资格进行向量化。而且你还需要以某种方式获得多版本化。既然如此,我们就来谈谈这个... ### `multiversion` cratehttps://shnatsel.github.io/state-of-simd-rust-2026/#the-multiversion-crate 下面讨论的一体化SIMD crate也提供多版本化,但让我们快速看看`multiversion` (https://crates.io/crates/multiversion),因为它对自动向量化最有用。它非常易于使用:你只需在函数上添加`#[multiversion(targets = "simd")]`注解即可。但这种便利隐藏了一个未记录的陷阱:调用带有`#[multiversion]`注解的函数会有一点开销。开销很小——不到一打指令,但如果你把它放在一个本身很小的函数上,就会显得开销显著。经验法则是:如果你的函数中有循环,就添加`#[multiversion]`;如果它只处理少数几个值,就添加`#[inline(always)]`,只要在调用链的某处存在`#[multiversion]`即可。下面列出的其他crate没有这个陷阱,也不需要你考虑函数大小,代价是更多的样板代码。 `multiversion`是唯一允许你列出所需确切CPU扩展的crate,而不是选择预定义的SIMD级别。因此,如果你的代码恰好受益于某个非常新的指令,你可以选择启用它。但根据我的经验,对于自动向量化代码,这很少发生。对于AVX-512,`multiversion`只检查它是否存在,而不检查它是否真的快,这在实践中可能损害性能(下文详述)。你可以通过样板代码来规避这一点——你必须在每个函数上都加上这个: ``` #[multiversion::multiversion(targets( "x86_64+cmpxchg16b+popcnt+sse3+sse4.1+sse4.2+ssse3", // x86_64-v2 "x86_64+avx+avx2+bmi1+bmi2+cmpxchg16b+f16c+fma+lzcnt+movbe+popcnt+sse3+sse4.1+sse4.2+ssse3+xsave", // x86_64-v3 "x86_64+fxsr,adx,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi1,bmi2,cmpxchg16b,fma,gfni,lzcnt,movbe,pclmulqdq,popcnt,vpclmulqdq,xsave,xsavec,xsaveopt,xsaves", // Ice Lake及以后 ))] ``` ## 可移植SIMD抽象https://shnatsel.github.io/state-of-simd-rust-2026/#portable-simd-abstractions 有几种生产就绪的方案。理想特性是: - **固定宽度向量:** 使用`f32x4`、`u8x16`等(已知大小)编写代码 - **硬件宽度向量:** 使用硬件支持的最大向量大小,无需预先知道 - **元素类型泛型:** 编写对`f32x4`和`f64x2`都有效的代码 - **向量宽度泛型:** 编写对`f32x4`、`f32x8`、`f32x16`都有效的代码 简要对比表: std::simd (nightly) fearless simd wide pulp macerator multiversioning 📦/🛠️ ✅ ❌ ✅ ✅ 固定宽度向量 ✅ ✅ ✅ ☑️ ❌ 硬件宽度向量 📦/🛠️ ✅ 🛠️ ✅ ✅ 元素类型泛型 ✅ ✅ 🛠️ 🛠️ ✅ 向量宽度泛型 ✅ ✅ 🛠️ ✅ ✅ 安全访问内部函数 🛠️ ✅ ☑️ ✅ 🛠️ 三角函数 📦/🛠️ 🛠️ ☑️ 🛠️ 🛠️ - ✅ 是 - ☑️ 是,但有限制 - 📦 是,通过第三方crate - 🛠️ 需自行构建 - ❌ 绝不支持 指令集支持情况: std::simd fearless simd wide pulp macerator SSE2 ✅ ✅ ✅ ☑️ 🐌 SSE4.x ✅ ✅ ✅ ☑️ ✅ AVX2 ✅ ✅ ✅ ✅ ✅ AVX-512 ✅ ✅ ✅ ✅ ✅ NEON ✅ ✅ ✅ ✅ ✅ WASM ✅ ✅ ✅ ✅ ✅ 其他全部 ✅ 🐌 🐌 🐌 🐌 *- ✅ 具有优化例程 - ☑️ 已实现但未使用。需要编写自定义分派以选择启用。 - 🐌 依赖自动向量化,通常较慢 * macerator也支持LoongArch,因为作者“闲着没事干”。 ### std::simdhttps://shnatsel.github.io/state-of-simd-rust-2026/#std-simd std::simd (https://doc.rust-lang.org/std/simd/index.html)并非SIMD的完整解决方案。它更像是一组必须放在标准库中的构建模块,其余一切交由生态系统的crate处理。最大的缺点是它仅在nightly版本可用,且仍会偶尔进行破坏性API更改。因此某天你更新编译器后,你的代码可能无法编译,需要去修复。但只要你能接受这一点,并且只需要固定宽度向量和可能的多版本化,它就相当不错! `std::simd`存在的*理由*是它直接位于LLVM之上,可以针对LLVM能支持的任何平台,包括只有大型银行使用的奇怪CPU或只有中国政府使用的CPU。另一方面,如果LLVM内部没有`std::simd`可以使用的完全匹配的操作,就没有B计划,实际上没有使用SIMD (https://shnatsel.github.io/improving-std-simd-swizzle-dyn/)。这种情况发生的频率令人不安。其`sin()`函数再贴切不过了:以SIMD形式发布标量实现是最大的罪过。而`reduce_sum()`在性能和准确性方面都糟糕透顶。因此别费心在浮点数上使用任何非平凡函数。 我们拥有的最接近正确三角函数的东西是sleef (https://crates.io/crates/sleef) crate,它是SLEEF (https://github.com/shibatch/sleef) 到`std::simd`的部分移植,只是有点小bug (https://github.com/burrbull/sleef-rs/issues/43) (https://github.com/burrbull/sleef-rs/issues/44)。而这已经是我在整篇文章中能找到的最好的三角函数了! 在多版本化方面,`std::simd`具有独特的灵活性。你可以使用multiversion (https://crates.io/crates/multiversion) crate或本节中任何其他SIMD crate的多版本化功能。所有其他crate只能与它们内置的多版本化机制配合工作。 它的`Simd` API看起来非常优雅,如果你能直接在`N`上做数学运算的话效果会很好,但你不能 (https://rust-lang.github.io/project-const-generics/documents/min_const_generics_plan.html)。即使在nightly版本上,该功能也非常不完善。没有它,使用`Simd`获取硬件大小的向量是可行的,但非常难看 (https://gist.github.com/Shnatsel/edc642125ac73fa7c365216c3a938802)。 虽然你可以在许多情况下直接使用`std::simd`并获得不错的性能,但通过第三方crate零散地添加功能只能达到这种程度。举个例子,`sleef` crate (https://crates.io/crates/sleef)无法与`multiversion` crate (https://crates.io/crates/multiversion)配合使用,你必须fork `sleef`并自己将其整合。第三方扩展可以独立工作,但无法组合。你需要的是一个所有部分协同工作的一体化解决方案。说到这个... ### fearless_simdhttps://shnatsel.github.io/state-of-simd-rust-2026/#fearless-simd Fearless SIMD (https://crates.io/crates/fearless_simd)是一个所有部分协同工作的一体化解决方案。看看那个在表格中漂亮的绿色对勾列吧!除了表格,`fearless_simd`独有的特性是: 1. 由于巧妙的设计 (https://shnatsel.github.io/safe-simd-in-rust-even-on-the-inside/),底层`unsafe`代码比其他crate少了几个数量级。 2. 多版本化开箱即用,即使对于微小函数也是如此。只需在函数上添加`#[simd]`就完成了。如果你讨厌过程宏,也有手动模式可用。 3. 多版本化由构建最终二进制文件的人控制。你无需修改库即可进行配置 (https://github.com/linebender/fearless_simd/tree/main/fearless_simd#multiversioning-on-x86)。 主要缺点是样板代码:你不是写 ``` fn my_func(a: A, b: B) { ``` 而是必须写 ``` #[simd] fn my_func(simd: S, a: A, b: B) { ``` 这有点啰嗦。AVX-512仅在较新的CPU上使用,不会损害性能(见下面的硬件部分)。你可以手动配置`multiversion`使其表现得像这样,但这不是默认设置,且需要大量样板代码(见上文)。所有其他SIMD抽象crate只是检查AVX-512是否存在。 它最近发布了v1.0版本,甚至还有安全策略 (https://github.com/linebender/fearless_simd/blob/main/fearless_simd/SECURITY.md)。最大的差距是三角函数。目前还没有像SLEEF这样的库移植到`fearless_simd`机制上。 ### widehttps://shnatsel.github.io/state-of-simd-rust-2026/#wide `wide`有很多优点:良好的平台覆盖、大量已实现的操作,而且它已经是v1.0版本了。它甚至有三角函数,尽管其精度明确未指定 (https://docs.rs/wide/1.7.0/wide/struct.f32x16.html#method.sin)。 最大的缺点是它与多版本化根本不兼容。如果你不针对x86,或者总是为已知硬件使用`-C target-cpu=`构建,这没问题,否则会严重损害性能。唯一的变通方法是`cargo multivers` (https://github.com/ronnychevalier/cargo-multivers/),但它只适用于长时间运行的程序,否则其启动成本会超过SIMD带来的性能收益。 另一个缺点是不支持任何形式的泛型,无论是元素类型还是向量宽度。不过,你可以用宏来变通。不要让函数泛型化,而是将其包装在`macro_rules!`中,并编写`$type::from_slice`而不是`T::from_slice`。它增加了一点样板代码,但消除了泛型约束的样板代码,所以得失参半。我做过,还不算太糟,尤其是当你引入类似pulp的库时。

相似文章

Rust 中的安全 SIMD,即使内部也安全

Lobsters Hottest

Rust 的 SIMD 抽象现在允许在不使用 unsafe 代码的情况下安全使用,这得益于 Rust 1.87 引入的 CPU 特性令牌,从而实现了简洁且可移植的向量操作。

Rust SIMD on the GPU

Hacker News Top

VectorWare announces that Rust's portable SIMD (core::simd) now works on the GPU, mapping SIMD vectors to warp lanes and enabling familiar Rust abstractions for GPU programming.

Fearless SIMD v1.0 已发布

Lobsters Hottest

Fearless SIMD v1.0 正式发布,为Rust提供安全且高性能的SIMD抽象,最小化不安全代码,同时提供可移植操作和平台内部函数。

Show HN: SIMD Viterbi Decoder in Rust

Hacker News Top

A Rust crate implementing Viterbi and Reed-Solomon forward error correction with SIMD acceleration, achieving faster throughput than the C library libfec on supported codecs.