bigywater.tech
Rust · 125

LLVM IR 与优化

Rust 的性能很大程度上来自 LLVM 的优化。这篇用 LLVM IR 把「优化」变成看得见的东西:栈槽怎么变成寄存器、循环怎么被向量化、函数调用怎么被内联,以及为什么 -O3 的代码并不一定最短。

3 → 0
sum 函数 IR 里 alloca 的数量(O0 → O3)
4 → 0
caller 里 call 指令的数量(O0 → O3,内联的结果)
17 对 71
opt-level=1 与 2 的汇编指令数(2 更大:向量化)

O0 与 O3 的 IR

实测 · 本机执行输出
  O0:行数 40  alloca 3  load 5  store 5  phi 0  向量类型 <4 x i32> 0  add 0
  O3:行数 89  alloca 0  load 6  store 0  phi 14  向量类型 <4 x i32> 21  add 15
  O3 的 sum 里与向量相关的几行:
    br i1 %min.iters.check, label %bb3.preheader, label %vector.main.loop.iter.check

MIR 是 LLVM IR 的前一层,见MIR 是什么。

O0 的 IR 长什么样

实测 · 本机执行输出
    start:
      %_5 = alloca [8 x i8], align 8
      %iter = alloca [16 x i8], align 8
      %s = alloca [4 x i8], align 4
      store i32 0, ptr %s, align 4
      %0 = call { ptr, ptr } @_RNvXs_NtNtCsedRpiqSkYaQ_4core5slice4iterRSlNtNtNtNtB8_4iter6traits7collect12IntoIterator9into_iterCsdo2
      %_3.0 = extractvalue { ptr, ptr } %0, 0
      %_3.1 = extractvalue { ptr, ptr } %0, 1
      store ptr %_3.0, ptr %iter, align 8
      %1 = getelementptr inbounds i8, ptr %iter, i64 8
      store ptr %_3.1, ptr %1, align 8
      br label %bb2
    bb2:                                              ; preds = %bb7, %start
      %2 = call align 4 ptr @_RNvXs2J_NtNtCsedRpiqSkYaQ_4core5slice4iterINtB6_4IterlENtNtNtNtBa_4iter6traits8iterator8Iterator4nextCsd
      store ptr %2, ptr %_5, align 8
      %3 = load ptr, ptr %_5, align 8
      %4 = ptrtoint ptr %3 to i64
      %5 = icmp eq i64 %4, 0

内联

实测 · 本机执行输出
  O0:caller 的 IR 12 行,call 指令 4 条:['helper', 'helper', 'llvm.sadd.with.overflow.i32', 'panic_const_add_overflow']
  O3:caller 的 IR 6 行,call 指令 0 条:[]

单态化生成的代码见Monomorphization 生成的代码。

优化级别与体积

实测 · 本机执行输出
  opt-level=0:汇编指令 146 条
  opt-level=1:汇编指令  17 条
  opt-level=2:汇编指令  71 条
  opt-level=3:汇编指令  71 条
  opt-level=s:汇编指令  17 条
  opt-level=z:汇编指令  15 条

LTO 对体积的影响见LTO 与二进制体积。指令数来自 aarch64 汇编,x86_64 会不同。

分步演示

LLVM IR 与优化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。IR 来自 --emit=llvm-ir,debuginfo=0
    • 没有验证:LLVM pass 的具体顺序(-print-pipeline-passes)、-C remark 的优化备注(在本机没有输出)、PGO 与 LTO 对内联决策的影响、x86_64 上的向量宽度(这里是 aarch64 的 NEON <4 x i32>)
    京ICP备2026067502号-1京公网安备11010502063487号