asm
对Rust生成汇编的分析 以及对Rust编译器的分析
本篇会讨论 如何写出高性能的Rust代码
本篇分为几个章节
良好的优化习惯: 讲述了良好的习惯来让生成的汇编更快
汇编观察: 对生成的汇编的优化进行观察与思考
工具链
cargo-asm
将函数生成汇编
首先我们需要安装cargo-asm工具
然后我们可以对函数生成汇编
cargo asm crate::function --rust
cargo asm crate::Type::method --rust
尽量使用#[inline(never)] 否则一些函数会被编译器内联而使rust-asm看不到汇编
良好的优化习惯
尽量使用开区间Range
前闭后开是计算机领域最好的表示法
Rust编译器会对开区间Range有优化 这是由于C语言不流行闭区间RangeInclusive
出现这种情况的原因是,包含范围需要进行特殊检查,以处理类似这样的情况1..=i32::MAX,这种情况无法用排除范围来表示。
使用for循环时,Rust 会反复.next()调用迭代器。每次调用时,它都必须检查当前迭代是否是上一次迭代的特殊情况。而使用.fold()for 循环.for_each(),迭代器可以遍历所有迭代1..n,然后单独执行n最后一次迭代,这更容易被 LLVM 优化。
代码与汇编
这里贴出一个前n项和的代码
pub fn sum_to_n(n: i32) -> i32 {
let mut s = 0;
for i in 1..=n{
s += i;
}
s
}
pub fn sum_to_n(n: i32) -> i32 {
let mut s = 0;
for i in 1..n+1{
s += i;
}
s
}
两段代码对应的x86汇编是
// 判断edi寄存器是否为0
test edi, edi
// 为0则跳转到.LBB0_1
jle .LBB0_1
// 将ecx的值复制为1
mov ecx, 1
// eax=0
xor eax, eax
.LBB0_3:
// edx=0
xor edx, edx
// 比较ecx - edi与0 写入标志位
cmp ecx, edi
// 将标志位结果写入sil
setl sil
add eax, ecx
cmp ecx, edi
jge .LBB0_5
mov dl, sil
add ecx, edx
cmp ecx, edi
jle .LBB0_3
.LBB0_5:
ret
.LBB0_1:
xor eax, eax
ret
xor eax, eax
test edi, edi
jle .LBB1_4
cmp edi, 1
je .LBB1_3
lea eax, [rdi, -, 2]
lea ecx, [rdi, -, 3]
imul rcx, rax
shr rcx
lea eax, [rcx, +, 2*rdi]
add eax, -3
.LBB1_3:
add eax, edi
.LBB1_4:
ret
benchmark
在实验下
Benchmarking sum/rangeinclusive: Collecting 100 samples in estimated 5.0001 sum/rangeinclusive time: [18.604 ns 18.618 ns 18.637 ns]
Found 12 outliers among 100 measurements (12.00%)
5 (5.00%) high mild
7 (7.00%) high severe
sum/range time: [1.5075 ns 1.5077 ns 1.5079 ns]
Found 4 outliers among 100 measurements (4.00%)
4 (4.00%) high mild
sum/formula time: [1.0059 ns 1.0071 ns 1.0092 ns]
Found 16 outliers among 100 measurements (16.00%)
3 (3.00%) high mild
13 (13.00%) high severe
我们观察到
直接使用 \(n(n+1)/2\)的formula函数均值为1.007ns
使用闭区间的rangeinclusive函数的均值为18.618ns
使用开区间的range函数均值为1.5007ns
汇编观察
整数除法
我们贴出最简单的1+...+n的通项的代码
#[inline(never)]
pub fn formula(n: i32) -> i32 {
n * (n+1)/2
}
它生成的汇编为
lea ecx, [rdi, +, 1]
imul ecx, edi
mov eax, ecx
shr eax, 31
add eax, ecx
sar eax
ret