我有这两个源文件:
const ARR_LEN: usize = 128 * 1024;
pub fn plain_mod_test(x: &[u64; ARR_LEN], m: u64, result: &mut [u64; ARR_LEN]) {
for i in 0..ARR_LEN {
result[i] = x[i] % m;
}
}
以及
#include <stdint.h>
#define ARR_LEN (128 * 1024)
void plain_mod_test(uint64_t *x, uint64_t m, uint64_t *result) {
for (int i = 0; i < ARR_LEN; ++ i) {
result[i] = x[i] % m;
}
}
我的C代码是一个很好的近似 rust 代码?
当我在www.example.com x86_64 gcc12.2 -O3
上编译C代码时godbolt.org,我得到了一个合理的结果:
plain_mod_test:
mov r8, rdx
xor ecx, ecx
.L2:
mov rax, QWORD PTR [rdi+rcx]
xor edx, edx
div rsi
mov QWORD PTR [r8+rcx], rdx
add rcx, 8
cmp rcx, 1048576
jne .L2
ret
但是当我对rustc 1.66 -C opt-level=3
执行同样的操作时,我得到了以下冗长的输出:
example::plain_mod_test:
push rax
test rsi, rsi
je .LBB0_10
mov r8, rdx
xor ecx, ecx
jmp .LBB0_2
.LBB0_7:
xor edx, edx
div rsi
mov qword ptr [r8 + 8*rcx + 8], rdx
mov rcx, r9
cmp r9, 131072
je .LBB0_9
.LBB0_2:
mov rax, qword ptr [rdi + 8*rcx]
mov rdx, rax
or rdx, rsi
shr rdx, 32
je .LBB0_3
xor edx, edx
div rsi
jmp .LBB0_5
.LBB0_3:
xor edx, edx
div esi
.LBB0_5:
mov qword ptr [r8 + 8*rcx], rdx
mov rax, qword ptr [rdi + 8*rcx + 8]
lea r9, [rcx + 2]
mov rdx, rax
or rdx, rsi
shr rdx, 32
jne .LBB0_7
xor edx, edx
div esi
mov qword ptr [r8 + 8*rcx + 8], rdx
mov rcx, r9
cmp r9, 131072
jne .LBB0_2
.LBB0_9:
pop rax
ret
.LBB0_10:
lea rdi, [rip + str.0]
lea rdx, [rip + .L__unnamed_1]
mov esi, 57
call qword ptr [rip + core::panicking::panic@GOTPCREL]
ud2
如何编写Rust代码,使其编译为类似于gcc for C所生成的汇编程序?
更新:当我用clang 12.0.0 -O3
编译C代码时,我得到的输出看起来更像Rust程序集,而不是GCC/C程序集。
这看起来像是GCC与Clang的问题,而不是C与Rust的差异。
plain_mod_test: # @plain_mod_test
mov r8, rdx
xor ecx, ecx
jmp .LBB0_1
.LBB0_6: # in Loop: Header=BB0_1 Depth=1
xor edx, edx
div rsi
mov qword ptr [r8 + 8*rcx + 8], rdx
add rcx, 2
cmp rcx, 131072
je .LBB0_8
.LBB0_1: # =>This Inner Loop Header: Depth=1
mov rax, qword ptr [rdi + 8*rcx]
mov rdx, rax
or rdx, rsi
shr rdx, 32
je .LBB0_2
xor edx, edx
div rsi
jmp .LBB0_4
.LBB0_2: # in Loop: Header=BB0_1 Depth=1
xor edx, edx
div esi
.LBB0_4: # in Loop: Header=BB0_1 Depth=1
mov qword ptr [r8 + 8*rcx], rdx
mov rax, qword ptr [rdi + 8*rcx + 8]
mov rdx, rax
or rdx, rsi
shr rdx, 32
jne .LBB0_6
xor edx, edx
div esi
mov qword ptr [r8 + 8*rcx + 8], rdx
add rcx, 2
cmp rcx, 131072
jne .LBB0_1
.LBB0_8:
ret
2条答案
按热度按时间k7fdbhmy1#
不要把苹果比作橙子蟹。
汇编输出之间的大部分差异是由于循环展开,rustc使用的LLVM代码生成器比GCC的代码生成器更积极地展开循环,并解决了CPU性能缺陷as explained in Peter Cordes’ answer。
这和Rust的版本差不多。
将Clang与
-Os
一起使用会使汇编更接近GCC:-C opt-level=s
与rustc的关系也是如此:当然,仍然需要检查
m
是否为零,这会导致一个异常分支,你可以通过缩小参数的类型来排除零来消除这个分支:现在函数将发出与Clang相同的程序集。
nszi6y052#
rustc
使用LLVM后端优化器,因此与clang
进行比较。LLVM默认展开小循环。最近的LLVM也在冰湖之前针对英特尔CPU进行了调整,其中
div r64
比div r32
慢得多,慢得多,值得对其进行分支。检查
uint64_t
是否实际适合uint32_t
,并对div
使用32位操作数大小。shr
/je
执行if ((dividend|divisor)>>32 == 0) use 32-bit
以检查两个操作数的高半部分是否全为零。如果检查m
的高半部分一次,并生成两个版本的循环,测试会更简单。2但是这段代码无论如何都会成为除法器吞吐量的瓶颈。这种投机取巧的
div r32
代码生成最终会过时,因为Ice Lake的整数除法器足够宽,不需要为64位处理更多的微操作,所以性能只取决于实际值,而不管它上面是否有额外的32位零。但是英特尔销售了很多基于Skylake的CPU(包括Cascade Lake服务器和Comet Lake的客户端CPU),虽然这些CPU仍然在广泛使用,LLVM
-mtune=generic
可能应该继续这样做。有关详细信息: