为何 Rust 循环无法自动向量化?

Trying to Make a Loop Auto-Vectorize

我尝试让一个计算点积的 Rust 循环自动向量化,却发现编译器在浮点数场景下完全放弃了 SIMD 优化。即便开启了 AVX2 和 FMA,生成的汇编代码依然在使用标量指令。根本原因在于 IEEE 754 浮点运算不满足结合律,编译器不敢随意重排加法顺序,否则可能改变计算结果。有趣的是,换成整数版本后,编译器立刻就能完美向量化。最终,我通过手动将数组按 4 个元素分块,成功骗过编译器实现了向量化,但这又限制了利用更宽的 256 位 AVX 寄存器。这篇文章揭示了自动向量化在浮点运算中的隐形陷阱。

如果你想要 100% 确定某段代码正在使用向量化 CPU 指令,除非你愿意在代码每次变更时都去检查汇编代码,否则你根本不能依赖自动向量化。
  1. dzdt

    Matt Pharr(SIMD 编程语言 ISPC 的作者)一针见血地指出:“自动向量化不是一种编程模型。”

    正如 Matt 所写([1]):

    “自动向量化器的问题在于,只要向量化可能失败(而且它确实会失败),那么如果你是一个真正关心编译器为你的程序生成什么代码的程序员,你就必须深入理解这个自动向量化器。然后,当它无法对你想要向量化的代码进行向量化时,你要么得用正确的方式‘戳’它,要么就得用正确的方式修改你的程序,让它再次为你工作。这是一种糟糕的编程方式;这完全是炼金术和猜测,你需要对单个编译器实现的细微差别变得高度专业化——而这是你本来完全不需要关心的东西。”

    [1] https://pharr.org/matt/blog/2018/04/18/ispc-origins

  2. gnufx

    当然,向量化并不意味着 SIMD。最早的向量化编译器是针对 CDC(?) 系统的,那远早于 SIMD 的出现。如今,比如 Arm 的 SVE 就与 SIMD Neon 截然不同。

    总之,我更熟悉在 C(和 Fortran)中优化数值循环,而不是 Rust。我很少见到单纯使用 SIMD 内联函数能奏效,而 GCC 的自动向量化在相同语义下(例如归约运算中的数值等价性)却不行。大多数情况下,你可以依靠 -fassociative-math 选项,当然,这不会牺牲峰值性能,例如 BLIS 在开启该选项时通过了其广泛的测试,但当然你应该检查一下。(GCC 也记录该选项对于让 Arm(Neon?)进行任何向量化都是必要的。)大多数时候,当人们告诉你 Intel 编译器好多少时,往往是因为它错误地将某些类似 -funsafe-math 的选项设为默认。

    不管怎样,GCC(像其他编译器一样)会通过 -fopt-info- 选项告诉你关于向量化的情况,无需检查汇编代码,你可能会发现一些惊喜。例如,你在 C 中使用 unsigned 作为循环索引,因为你确定它们是正数,却看到因“循环非仿射”而导致向量化失败,原因在于 C 的溢出语义;改用有符号类型即可。

    使用正确优化的数值库(通常是 BLAS)的另一个原因是,至少对于三级(矩阵-矩阵)操作而言。即使你为内存层次结构正确设置了分块(blocking),通常仅靠向量化也无法获得峰值性能,因为三……

同日更多故事

2026-09-14