Rust自动向量化优化规则咨询:多段代码向量化差异疑问
测试环境:MacBook Pro M1,编译命令:cargo rustc --bin rust-playground -- -C opt-level=3 --emit=llvm-ir,通过LLVM-IR中的向量指令(如add <4 x i32>、mul <4 x i32>)判断是否被自动向量化。
案例1:简单求和未被向量化
调用代码(前3个案例共用):
fn main() { let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, ]; println!("{:?}", auto_vec(&a)); }
目标函数代码:
pub fn auto_vec(a: &[i32]) -> i32 { let mut p = 0; for i in a { p += i; } p }
现象:LLVM-IR中无向量加法指令,未被自动向量化。
原因:单变量求和的计算密度低,M1的NEON标量加法延迟更低,编译器认为标量实现的性能更优;同时该函数是独立对外暴露的,编译器无法确定输入数组的最小长度,出于保守选择不触发向量化。
案例2:同时求和与平方和被向量化
目标函数代码:
pub fn auto_vec(a: &[i32]) -> i32 { let mut p = 0; let mut q = 0; // can see some instructions like 'add <4 x i32>' and 'mul <4 x i32>' for i in a { p += i; q += i * i; } q - p }
现象:LLVM-IR中出现add <4 x i32>和mul <4 x i32>向量指令,被自动向量化。
原因:循环内同时进行两个独立的计算任务(求和、平方和),NEON向量单元可以并行处理这两个操作,整体吞吐量提升明显。此时向量化的收益远超过额外开销,编译器因此触发自动向量化。
案例3:计算结果未使用时未被向量化
目标函数代码:
pub fn auto_vec(a: &[i32]) -> i32 { let mut p = 0; let mut q = 0; // can't see some instructions like 'add <4 x i32>' or 'mul <4 x i32>' for i in a { p += i; q += i * i; } // do not access p and q 0 }
现象:LLVM-IR中无向量指令,未被自动向量化。
原因:编译器的*死代码消除(DCE)*优化会识别到p和q的计算结果未被使用,直接将整个循环代码完全删除,自然不会生成任何向量化指令。查看LLVM-IR会发现,函数仅保留返回0的逻辑。
案例4:函数内联后部分向量化
代码:
fn main() { // can see store <4 x i32> let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, ]; let mut p = 0; let mut q = 0; // can't see add <4 x i32> or mul <4 x i32> for i in a { p += i; q += i * i; } println!("{:?}", q - p); }
现象:LLVM-IR中出现store <4 x i32>但无向量加法/乘法指令。
原因:函数内联后,编译器能识别到数组a是编译期常量,直接进行常量折叠优化——提前计算出q-p的最终结果,循环代码被完全消除,因此看不到向量运算指令;而数组初始化时,由于是固定长度的常量数组,被优化为向量存储指令以提升初始化效率。
数组长度对向量化的影响
当数组过短时,编译器不会进行向量化优化,原因是:向量化需要额外开销,比如循环头尾的边界处理(处理无法被向量长度整除的元素)、向量寄存器的初始化与加载存储等。如果数组长度太小,这些额外开销会超过向量化带来的性能提升,因此编译器会选择标量实现,避免得不偿失。一般编译器会设置一个阈值(通常为向量宽度的2-4倍),只有当数组长度超过该阈值时才会考虑向量化。
内容的提问来源于stack exchange,提问作者doki

