我目前正在學習 Rust,作為第一個練習,我想實作一個計算第 n 個斐波那契數的函式:
fn main() {
for i in 0..48 {
println!("{}: {}", i, fibonacci(i));
}
}
fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) fibonacci(n - 2),
}
}
我將其運行為:
$ time cargo run --release
real 0m15.380s
user 0m15.362s
sys 0m0.014s
作為練習,我還在 C 中實作了相同的演算法。我期待類似的性能,但 C 代碼在 80% 的時間內運行:
#include<iostream>
unsigned int fibonacci(unsigned int n);
int main (int argc, char* argv[]) {
for(unsigned int i = 0; i < 48; i) {
std::cout << i << ": " << fibonacci(i) << '\n';
}
return 0;
}
unsigned int fibonacci(unsigned int n) {
if(n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fibonacci(n - 1) fibonacci(n - 2);
}
}
編譯為:
$ g test.cpp -o test.exe -O2
并運行:
$ time ./test.exe
real 0m12.127s
user 0m12.124s
sys 0m0.000s
為什么我會在性能上看到如此差異?我對在 Rust 中更快地計算斐波那契不感興趣(使用不同的演算法);我只對差異來自哪里感興趣。這只是我學習 Rust 程序中的一個練習。
uj5u.com熱心網友回復:
TL;DR:這不是 Rust 與 C ,而是 LLVM (Clang) 與 GCC。
不同的優化器以不同的方式優化代碼,在這種情況下,GCC 生成更大但更快的代碼。
這可以使用godbolt進行驗證。
這是Rust,使用 GCC 編譯(通過 rustgcc-master):
example::fibonacci:
push r15
push r14
push r13
push r12
push rbp
xor ebp, ebp
push rbx
mov ebx, edi
sub rsp, 24
.L2:
test ebx, ebx
je .L1
cmp ebx, 1
je .L4
lea r12d, -1[rbx]
xor r13d, r13d
.L19:
cmp r12d, 1
je .L6
lea r14d, -1[r12]
xor r15d, r15d
.L16:
cmp r14d, 1
je .L8
lea edx, -1[r14]
xor ecx, ecx
.L13:
cmp edx, 1
je .L10
lea edi, -1[rdx]
mov DWORD PTR 12[rsp], ecx
mov DWORD PTR 8[rsp], edx
call example::fibonacci.localalias
mov ecx, DWORD PTR 12[rsp]
mov edx, DWORD PTR 8[rsp]
add ecx, eax
sub edx, 2
jne .L13
.L14:
add r15d, ecx
sub r14d, 2
je .L17
jmp .L16
.L4:
add ebp, 1
.L1:
add rsp, 24
mov eax, ebp
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
ret
.L6:
add r13d, 1
.L20:
sub ebx, 2
add ebp, r13d
jmp .L2
.L8:
add r15d, 1
.L17:
add r13d, r15d
sub r12d, 2
je .L20
jmp .L19
.L10:
add ecx, 1
jmp .L14
并使用 LLVM(通過 rustc):
example::fibonacci:
push rbp
push r14
push rbx
mov ebx, edi
xor ebp, ebp
mov r14, qword ptr [rip example::fibonacci@GOTPCREL]
cmp ebx, 2
jb .LBB0_3
.LBB0_2:
lea edi, [rbx - 1]
call r14
add ebp, eax
add ebx, -2
cmp ebx, 2
jae .LBB0_2
.LBB0_3:
add ebx, ebp
mov eax, ebx
pop rbx
pop r14
pop rbp
ret
我們可以看到 LLVM 產生了一個簡單的版本——在回圈的每次迭代中呼叫該函式——而 GCC 通過行內一些呼叫來部分展開遞回。這導致在 GCC 的情況下呼叫次數較少,并且每個函式呼叫的開銷約為 5ns,這已經足夠重要了。
我們可以通過 Clang 和 GCC 使用 LLVM對 C 版本進行相同的練習,并注意結果非常相似。
因此,正如所宣布的,這是 LLVM 與 GCC 的區別,而不是語言的區別。
順便說一句,優化器可能會產生如此不同的結果,這就是為什么我對 rustc_codegen_gcc 計劃(在 Godbolt 上稱為 rustgcc-master)的進展感到非常興奮的原因,該計劃旨在將 GCC 后端插入 rustc 前端:一旦完成任何人將能夠為自己的作業負載切換到更好的優化器。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/420785.html
標籤:
上一篇:AMD架構的GPU計算平臺
