Sergey "Shnatsel" Davidoff 刚发布了年度调查《The state of SIMD in Rust in 2026》。他去年写完调查后就一头扎进去参与贡献,现在已经是 Fearless SIMD 的 maintainer 了。为了避免利益冲突,他把草稿发给 std::simd、wide、pulp、macerator 的作者们审阅,但保留了编辑权。

这份调查值得认真读的原因是:它不只是列一遍有哪些 crate,而是把整个技术栈从「编译器能不能向量化」一直扒到「Intel 的 AVX-512 在哪些 CPU 上反而更慢」。下面是我读完之后的梳理,加上一些我自己踩过的坑。

三条路线,以及它们的真实成本

用上 SIMD 只有三种方式,作者把它们排了个序:

// 1. 让编译器自己向量化
let sum: i32 = slice.iter().sum();

// 2. 可移植的 SIMD 抽象
let c = i32x4::splat(3) + i32x4::from_array([1,2,3,4]);

// 3. 平台专属 intrinsic,且必须处理多版本分发
#[cfg(all(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)

自动向量化最省事,没有依赖,能覆盖编译器支持的所有指令集。但它的不可靠程度也很真实:函数越大越复杂,编译器放弃向量化的概率越高;性能还会随编译器版本上下横跳。

这里有个容易忽略的细节:Rust 1.98 才稳定了 algebraic_add() 这类代数运算。在此之前,浮点求和根本无法自动向量化,因为改变运算顺序会改变可观测结果,编译器不被允许这么做。algebraic_* 相当于一个不那么危险的 -ffast-math,但你还是得手动改写代码才可能被向量化。

x86 的分发问题,以及 15% 的现实

x86_64 默认只保证 SSE2,所以你要么接受这个基线,要么处理多版本分发。

作者提到 multiversion crate 有个没写进文档的坑:给函数加 #[multiversion(targets = "simd")] 之后,调用它会有十几条指令的开销。如果这个函数本身很小,开销就直接吞掉收益。他的经验法则是:函数里有循环就加 #[multiversion],只处理几个值就加 #[inline(always)],前提是调用链上游某处有 #[multiversion]。

但真正扎心的是这一段:AVX2 早在 2013 年就有 CPU 支持了,可 最近一款不支持 AVX2 的 Intel CPU 是 2021 年发布的。结果是 2026 年的 Firefox 硬件调查里,15% 的 x86 CPU 依然没有 AVX2。你还得为这批机器维护 SSE4.2 代码路径——那是 2008 年的指令集。

如果你只在自己服务器或云上跑,那就直接断言硬件够新:

RUSTFLAGS='-C target-cpu=x86-64-v3' cargo build --release

代价是遇到不支持的 CPU 会直接崩或行为异常,所以对外分发的二进制不能这么干。

值得注意的是,这个问题只存在于 x86。ARM 从 64 位开始就强制要求 NEON,WebAssembly 则让你编两份二进制再用 JS 探测。

crate 横评:一张表说清选择

作者对生产可用的可移植 SIMD 抽象做了特性矩阵,这张表的可读性比任何 benchmark 都高:

特性 std::simd fearless_simd wide pulp macerator
多版本分发 ✅ ✅ ❌ ✅ ✅
定宽向量 ✅ ✅ ✅ ☑️ ❌
硬件宽向量 部分 ✅ 手动 ✅ ✅
对元素类型泛型 ✅ ✅ ❌ ❌ ✅
对向量宽度泛型 ✅ ✅ ❌ ☑️ ✅
安全调用 intrinsic 部分 ✅ ☑️ ✅ ❌
三角函数 部分 ❌ ☑️ ❌ ❌

(✅ 完整支持,☑️ 有但有限制,❌ 不支持)

指令集覆盖方面,AVX-512 各家都支持,但实现深度差别很大,值得单独看几个关键点。

std::simd 的定位是「必须进标准库的积木块」,其余交给生态。最大问题是 nightly-only,而且还在不定期做破坏性 API 变更。它直接坐在 LLVM 之上,能覆盖任何 LLVM 能覆盖的平台,包括那些只有大型银行或者 LoongArch 才用的 CPU。但反过来,如果 LLVM 内部没有恰好对应的操作,std::simd 没有 plan B,最终就是一条 SIMD 指令都不发。作者说这种情况「令人不安地频繁」。它的 reduce_sum() 在性能和精度上都是最差的,浮点上的非平凡函数建议别用。

fearless_simd 是作者现在维护的那个,也是唯一所有格子全绿的。卖点有几个:底层 unsafe 代码比其他 crate 少几个数量级;#[simd] 标注上去就完事,连很小的函数也能正确分发;分发策略由编译最终二进制的那个使用者控制,不用改库源码。代价是样板代码:

#[simd]
fn my_func<S: Simd>(simd: S, a: A, b: B) {
    // ...
}

wide 平台覆盖好、操作全、已经 v1.0,甚至有三角函数(虽然精度明确不保证)。但它在架构上和分发机制不兼容,不跑 x86 或者总是带 -C target-cpu= 固定目标还行,否则性能受限。唯一的绕法是 cargo multivers,但那玩意只对长跑程序有效,短命程序的启动开销比 SIMD 收益还大。

pulp 是给 faer 线性代数库做的,所以 API 偏向原生宽度向量,实现的操作也偏数学(连 swizzle 都没有)。定宽向量理论上能用,但完全没有文档、非常别扭,作者在研究过程中还顺手修了一个 soundness bug。

macerator 是 pulp 的近亲,给 burn 做 CPU 后端。相对 pulp 加了对元素类型泛型,但砍掉了安全 intrinsic 访问和大部分文档。它是除 std::simd 之外唯一对 f16 有可移植操作的库。crates.io 上除了 burn 没人用它。

intrinsic 的永恒拉锯战

这一节是我觉得整篇文章最有价值的部分。作者直说:intrinsic 本该清晰地映射到具体 CPU 指令,简单又重要,你期待它工作得很好,但它不。Rust 不行,C++ 不行,C 也不行。

根本矛盾在于「给我这条确切指令」和编译器优化之间的冲突。把 intrinsic 当纯黑盒,别处就低效。

他举了个在 ARM 上真实踩到的 bug:u32x4::from([1,2,3,4]) 很慢。这只是在加载一个常量,而且 u32x4 的内存布局和 [u32; 4] 完全一样,本该就是一次 load。结果底层的 vld1_u32_x4 intrinsic 在编译器里是黑盒实现,LLVM 只能看到一个作用于栈上值的黑盒操作,生成的汇编变成:先把常量装进寄存器,再扔回栈上,然后通过黑盒的 vld1_u32_x4 再装回寄存器。修法是把这个 intrinsic 降级成普通 load 的兼容包装。

但把所有 intrinsic 都变成编译器操作的包装并不现实。它本质上是硬件操作,只是还没人为它写优化 pass。而且如果想让编译器理解这些操作(或者对略有差异的硬件指令做抽象),就需要拆成多个基本块;然后你又得靠优化器把它们重新组合回单条指令,而无关的优化模式很容易打乱这个匹配。结果就是:简单测试用例上能跑通的优化,真实代码里会崩。

再叠上指令选择 bug。作者遇到 LLVM 看到一个常量索引的 512 位 shuffle 操作,觉得「我知道,我能优化这个」,结果它的「优化」用的是 SSE4.2 时代的操作,比直接执行 shuffle 指令慢得多(已在 LLVM 23 修复)。他甚至维护了一份自己找到的编译器 bug 清单。Rust 相关的报上去修了,但影响 Rust、C++、C 的 LLVM bug 还有一堆没修。这不是 LLVM 独有的问题,GCC 类似,MSVC 明显比两个开源编译器更差。

附带一个冷知识:Intel 忘了把某些 AVX-512 指令放进他们可搜索的 Intrinsics Guide,导致大多数编译器没实现,现在从高层语言根本够不到这些指令。

那用内联汇编绕过去?可以,但内联汇编块是个货真价实的黑盒,所有优化问题加倍奉还,进出汇编块本身还有开销。写一个大函数还行,就想用一两条指令并不划算。

硬件真相:AVX-512 在部分 CPU 上是净亏损

这是标题里说的性能陷阱。作者写完 5 个不同指令集的 SIMD 代码之后,对硬件有一肚子意见。

AVX2 是最常见也最坑的指令集。 作者的说法很传神:每当他实现一个直白的 SIMD 操作,AVX-512 和 NEON 有一半时候有原生支持,而 AVX2 需要复杂又慢的模拟。原因是它压根不是真正的 256 位 ISA,更像是两个 128 位执行单元塞在一起。

然后 AVX-512 的问题更微妙。在早期支持 AVX-512 的 Intel CPU 上,即使只在一个核上跑,也会拉低所有核的频率,整个芯片的其他部分都得陪着降频。讽刺的是 AVX-512 只出现在高核心数的高端 CPU 上,而这种场景下频率外溢的伤害恰恰最大。你以为通吃全部核心能摊平,但更可能是撞上内存带宽瓶颈,CPU 在干什么已经不重要了。

这个降频行为直到 2019 年的 Ice Lake 才被修复,这也正是 fearless_simd 只在 Ice Lake 及以后(以及从来没有降频问题的 AMD)启用 AVX-512 的原因。

AMD 的设计从另一个角度证明了 Intel 有多失误:Zen 4 根本没有硬件 512 位运算单元,把大多数 512 位操作映射到 256 位执行单元上,benchmark 里照样吊打 Intel 的原生 512 位硬件。Zen 5 上了原生 512 位之后就彻底没悬念了。

scatter/gather 指令则是 AMD 的锅:Zen 上它们没有硬件原生实现,比发一堆小 load 还慢。而 LLVM 自动向量化时有时会为 AVX-512 生成 scatter/gather,在 Intel 上拖慢 1.75 倍,在 AMD 上拖慢 4 倍。作者都不确定 LLVM 图什么。

浮点还有个坑:对非常接近零的值做数学运算,性能会暴跌。AMD 大约慢 2 倍,Intel 上你会吃到 30 倍的减速。

现实分布数据来自 Steam 硬件调查:23.9% 的系统有 AVX-512(偏高端和游戏机,Firefox 图形调查里那 15% 只有 SSE4.2 的机器在这里只占 2%)。桌面端倒是不用担心那些「坏掉的 AVX-512」,所以把 Ice Lake 设成 AVX-512 的门槛实际上不亏,Skylake 服务器会退到 AVX2,正好避开降频。

ARM 这边,AVX2 做错的事 64 位 NEON 全做对了。 只有 128 位向量,你以为它对应 SSE4.2,实际更接近 AVX2:寄存器空间和 AVX2 一样(实践中这才是瓶颈),而且不需要你显式处理两个 128 位执行单元,大核通过指令级并行透明地并行执行 128 位操作,省电的小核则逐个执行。作者在 SIMD base64 解码上的实测:同一算法在 NEON 上比 AVX2 快 1.5 到 2 倍,但比 AVX-512 还是慢 2 倍。而且它是单一编程模型,64 位 ARM 上强制存在,完全不需要分发。

NEON 唯一的槽点是 ARM 芯片有大核小核两套执行特性完全不同的核心,一个在大核上快的指令序列在小核上就慢,无法针对具体 CPU 挑最优实现,只能权衡。

SVE 基本可以忽略,SVE2 现在是强制要求,但在高端服务器芯片上也只按 128 位宽实现,等于一个多了几步的别扭 NEON。

RISC-V 向量(RVV)在 2026 年完全不用管,因为支持向量的 RISC-V 硬件本身就不重要,性能价格比极差。而且就算有好硬件,spec 的写法让某些关键指令对编译器根本不可用。

编译器的功能愿望清单

作者列了几个对 Rust 最要紧的东西,按重要性排序:

min_generic_const_args 让数组能和硬件宽 SIMD 向量配合使用。目前只能拿超大数组、generic-array crate 或者宏来凑合,都不完整或者很丑。

Struct Target Features RFC 会让 fearless_simd / pulp / macerator 不再需要在函数上加 #[simd]。样板代码少了是其次,关键是你不会再因为忘了加注解而悄悄掉性能。

还需要一种让迭代器不和多版本分发冲突的办法,Struct Target Features RFC 也能解决这个。

标准库层面,他希望有类似 crater 的机制来验证 intrinsic 的改动,以及 std::simd 进 stable,那样生态 crate 能删掉大部分代码,顺便白拿所有冷门平台的支持。

我自己的取法

看完这篇,我把自己项目里的策略调整成这样:

别默认上 intrinsic。 先写紧凑的循环,用 as_chunks() 而不是裸切片迭代,然后看汇编确认编译器到底有没有向量化。只有确认它做不到,才考虑动手。

crate 选 fearless_simd 或 wide,取决于你是否需要对外分发。 需要分发且想省心就 fearless_simd,全绿的格子不是白拿的。只给固定硬件编译,wide 的 v1.0 成熟度和三角函数覆盖更舒服。

x86 上永远测两个目标。 SSE2 基线跑一遍,-C target-cpu=x86-64-v3 再跑一遍。那 15% 的差距你只有在真机上才看得到。

别信 AVX-512 在所有 CPU 上都更快。 如果目标是 Skylake 服务器,启用 AVX-512 很可能是在给自己降频,用 Ice Lake 作为门槛这个做法值得照抄。

内存带宽才是最终天花板。 当计算密集到一定程度,AVX-512 的优势会被内存吃光。如果你的 kernel 已经在带宽边界上,继续堆向量宽度是浪费时间,该去看数据布局。

这篇调查最让我意外的不是哪个 crate 好用,而是编译器对 intrinsic 的优化至今仍是一团乱麻。我们把 SIMD 当成性能银弹,但底下二十年攒出来的指令集碎片、降频行为、指令选择 bug,决定了同一段代码在不同机器上能差出 4 倍。写 SIMD 的合理心态大概是:把它当成需要实测的硬件适配工作,而不是一次写完到处都快的抽象。

原文:https://shnatsel.github.io/state-of-simd-rust-2026/