The state of SIMD in Rust in 2026
The state of SIMD in Rust in 2026
2026 年 Rust SIMD 现状
A lot of progress was made since last year, and I made some of it! After last year’s survey I started contributing to the SIMD library that seemed the most promising. One thing led to another, and now I’m a maintainer of Fearless SIMD. 自去年以来,Rust SIMD 领域取得了长足进步,其中也有我的一份贡献!在去年的调查之后,我开始为看起来最有前景的 SIMD 库做贡献。机缘巧合之下,我现在已经成为了 Fearless SIMD 的维护者之一。
To avoid a conflict of interest, I invited authors of other libraries (std::simd, wide, pulp, macerator) to review and provide feedback on a draft of this article. However, I retained editorial control, and all mistakes are my own. 为了避免利益冲突,我邀请了其他库(如 std::simd, wide, pulp, macerator)的作者审阅本文草稿并提供反馈。不过,我保留了最终编辑权,文中若有任何错误,均由我个人承担。
This year’s survey is more in-depth than my previous one. So buckle up, and let’s take it… from the top! 今年的调查比以往更加深入。所以,系好安全带,让我们从头开始吧!
What’s SIMD? Why SIMD?
什么是 SIMD?为什么要用 SIMD?
Hardware that does arithmetic is cheap, so any CPU made this century has plenty of it. But you still only have one instruction decoding block and it is hard to get it to go fast, so the arithmetic hardware is vastly underutilized. 执行算术运算的硬件非常廉价,因此本世纪生产的任何 CPU 都配备了大量的算术单元。但由于你只有一个指令解码模块,且很难让它运行得非常快,导致算术硬件的利用率往往严重不足。
To get around the instruction decoding bottleneck, you can feed the CPU a batch of numbers all at once for a single arithmetic operation like addition. Hence the name: “single instruction, multiple data,” or SIMD. 为了绕过指令解码的瓶颈,你可以一次性向 CPU 输入一批数字,进行诸如加法之类的单一算术运算。这就是它名称的由来:“单指令多数据”(Single Instruction, Multiple Data),即 SIMD。
Instead of adding two numbers together, you can add two batches or “vectors” of numbers and it takes about the same amount of time as doing just one addition. 你不再是仅仅将两个数字相加,而是将两批或两个“向量”数字相加,而这所花费的时间与执行一次加法几乎相同。
On recent x86 chips these batches can be up to 512 bits in size, so in theory you can get an 8x speedup for math on f64 or a 64x speedup on u8. In practice it can run both slower and faster. 在近期的 x86 芯片上,这些批次的大小可达 512 位,因此理论上你可以获得 8 倍的 f64 数学运算加速,或 64 倍的 u8 加速。但在实际应用中,性能表现可能会更快,也可能会更慢。
Instruction sets
指令集
Historically, SIMD instructions were added after the CPU architecture was already designed, so SIMD is an extension with its own marketing name on each architecture. 从历史上看,SIMD 指令是在 CPU 架构设计完成后才添加的,因此 SIMD 在每种架构上都是一种带有独立营销名称的扩展。
ARM calls theirs “NEON”, and all 64-bit ARM CPUs have it. ARM 将其称为“NEON”,所有 64 位 ARM CPU 都具备该功能。
WebAssembly doesn’t have a marketing department, so they just call theirs “WebAssembly 128-bit packed SIMD extension”. WebAssembly 没有营销部门,所以他们直接将其称为“WebAssembly 128 位打包 SIMD 扩展”。
64-bit x86 shipped with one called “SSE2” which has basic instructions for 128-bit vectors, but later they added a whole menagerie of extensions on top of that, with SSE 4.2 adding more operations, AVX and AVX2 adding 256-bit vectors and AVX-512 adding 512-bit vectors and even more operations. 64 位 x86 随附了一种名为“SSE2”的指令集,它具备 128 位向量的基本指令。但随后,他们在此基础上添加了一系列扩展:SSE 4.2 增加了更多操作,AVX 和 AVX2 增加了 256 位向量,而 AVX-512 则增加了 512 位向量以及更多的操作。
The word “later” in the above paragraph creates a problem. Does this CPU have that instruction? 上面段落中的“随后”一词引发了一个问题:这颗 CPU 是否具备该指令?
If you’re running a program on an x86_64 CPU, it’s not a given that the CPU has any particular SIMD extension. So by default the compiler isn’t allowed to use instructions beyond SSE2 because that won’t work on all x86_64 CPUs. 如果你在 x86_64 CPU 上运行程序,不能保证该 CPU 一定具备特定的 SIMD 扩展。因此,编译器默认不允许使用 SSE2 之外的指令,因为这无法在所有 x86_64 CPU 上运行。
There are two ways around this problem. 解决这个问题有两种方法。
If you work for a company that only ever runs their binaries on their own servers or on a public cloud, you can just assert that they’re all recent enough to at least have AVX2 that was introduced over 10 years ago, and have the program crash or misbehave if it ever runs on anything without AVX2: 如果你所在的公司只在自己的服务器或公有云上运行二进制文件,你可以断定这些机器足够新,至少支持 10 多年前推出的 AVX2。如果程序在不支持 AVX2 的机器上运行,直接让其崩溃或报错即可:
RUSTFLAGS='-C target-cpu=x86-64-v3' cargo build --release
However, if you are distributing the binaries for other people to run, that’s not really an option. 然而,如果你要分发二进制文件供他人运行,这显然不是一个可行的方案。
Instead you can do something called function multiversioning: compile the same function multiple times for different SIMD extensions, and when the program actually runs, check what features the CPU supports and select the appropriate version based on that. 取而代之的是,你可以使用一种称为“函数多版本化”(function multiversioning)的技术:针对不同的 SIMD 扩展多次编译同一个函数。当程序实际运行时,检查 CPU 支持哪些特性,并据此选择合适的版本。
Fortunately, this problem only exists on x86. 幸运的是,这个问题只存在于 x86 架构上。
ARM made NEON mandatory on its 64-bit CPUs and hasn’t really added useful SIMD extensions after that (more on that later). ARM 在其 64 位 CPU 上强制要求支持 NEON,并且在此之后并没有真正添加过有用的 SIMD 扩展(稍后会详细说明)。
WebAssembly makes you compile two different binaries, one with SIMD and one without, and use JavaScript to check if the browser supports SIMD. WebAssembly 则要求你编译两个不同的二进制文件,一个带 SIMD,一个不带,并使用 JavaScript 来检查浏览器是否支持 SIMD。
How do I SIMD?
我该如何使用 SIMD?
There are three ways to leverage SIMD: 利用 SIMD 有三种方式:
- Automatic vectorization:
&[i32].sum() - Portable SIMD abstractions:
i32x4 + i32x4 - Platform-specific intrinsics - hang on, we’re gonna need a bigger code block:
- 自动向量化:
&[i32].sum() - 可移植的 SIMD 抽象:
i32x4 + i32x4 - 平台特定的内联函数(Intrinsics)——等等,我们需要一个更大的代码块:
#[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)
Let’s look at what each one entails and what the state of each programming model is. 让我们看看每种方式的含义以及每种编程模型的现状。
Automatic vectorization
自动向量化
Just write plain Rust and let the compiler heuristics do the work! 只需编写普通的 Rust 代码,让编译器的启发式算法来完成剩下的工作!
You can get it to work quite well, if you are careful to write code in a way that the compiler can reliably(ish) vectorize. This usually involves iterating over &[i32].as_chunks() instead of &[i32] and benchmarking or staring at the assembly to verify it worked. See Can You Trust a Compiler to Optimize Your Code? for details.
如果你小心地以编译器能够(相对)可靠地向量化的方式编写代码,这种方法效果相当不错。这通常涉及遍历 &[i32].as_chunks() 而不是 &[i32],并进行基准测试或盯着汇编代码来验证它是否生效。详情请参阅《你能信任编译器来优化你的代码吗?》(Can You Trust a Compiler to Optimize Your Code?)。
This is the easiest option to use, requires no dependencies, and automatically supports all instruction sets the compiler supports, no matter how obscure. 这是最简单的选择,不需要任何依赖,并且自动支持编译器支持的所有指令集,无论它们多么冷门。
The downside is that this method is not very reliable. The larger and more complex your function is, the greater is the chance that the compiler will not be able to vectorize it. Performance can also swing wildly depending on the compiler version or due to changes to the surrounding code. 缺点是这种方法不太可靠。你的函数越大、越复杂,编译器无法对其进行向量化的可能性就越大。性能也可能根据编译器版本或周围代码的更改而剧烈波动。
Floating-point types also need special care. 浮点类型也需要特别注意。
Floats are weird. Even something as trivial as summing an array of floats with reasonable precision gets surprisingly involved, see Taming Floating-Point Sums. 浮点数很奇怪。即使是像以合理的精度对浮点数组求和这样简单的事情,也会变得出奇地复杂,请参阅《驯服浮点求和》(Taming Floating-Point Sums)。
Previously automatic vectorization didn’t work with floating-point types because it would change the precision of the result (often for the better, but the compiler is not permitted to change any observable results). 以前,自动向量化无法处理浮点类型,因为它会改变结果的精度(通常是变得更好,但编译器不允许改变任何可观察的结果)。
This changed in Rust 1.98 which stabilized algebraic ops such as algebraic_add() that let the compiler change the observable result, like a less dangerous -ffast-math. You still have to rewrite your code to use them for it to be eligible for vectorization in most cases.
这种情况在 Rust 1.98 中发生了改变,该版本稳定了诸如 algebraic_add() 之类的代数运算,允许编译器改变可观察的结果,类似于一种危害较小的 -ffast-math。在大多数情况下,你仍然需要重写代码以使用这些函数,才能使其具备向量化的资格。
And you still need to get multiversioning somehow. So while we’re at it… 而且你仍然需要以某种方式实现多版本化。所以,顺便提一下……
The ‘multiversion’ crate
’multiversion’ crate
The all-in-one SIMD crates discussed below also provide multiversioning, but let’s take a look at multiversion real quick since it’s most useful for automatic vectorization.
下面讨论的一体化 SIMD crate 也提供了多版本化功能,但让我们快速了解一下 multiversion crate,因为它对自动向量化最有用。
It’s very easy to use: you add the #[multiversion(targets = "simd")] annotation to your function and that’s it.
它非常易于使用:只需在函数上添加 #[multiversion(targets = "simd")] 注解即可。
But that ease hides an undocumented pitfall: calling a function annotated with #[multiversion] has a little bit of overhead. It is very small - under a dozen…
但这种简便性掩盖了一个未记录的陷阱:调用带有 #[multiversion] 注解的函数会有一些开销。虽然非常小——不到一打……