为什么这段代码生成的汇编比等效的 C++/Clang 多得多?
Why does this code generate much more assembly than equivalent C++/Clang?
我写了一个简单的 C++ 函数来检查编译器优化:
bool f1(bool a, bool b) {
return !a || (a && b);
}
之后我检查了 Rust 中的等价物:
fn f1(a: bool, b: bool) -> bool {
!a || (a && b)
}
我使用 godbolt 检查汇编程序输出。
C++ 代码(由带有 -O3 标志的 clang 编译)的结果如下:
f1(bool, bool): # @f1(bool, bool)
xor dil, 1
or dil, sil
mov eax, edi
ret
而 Rust 等效的结果要长得多:
example::f1:
push rbp
mov rbp, rsp
mov al, sil
mov cl, dil
mov dl, cl
xor dl, -1
test dl, 1
mov byte ptr [rbp - 3], al
mov byte ptr [rbp - 4], cl
jne .LBB0_1
jmp .LBB0_3
.LBB0_1:
mov byte ptr [rbp - 2], 1
jmp .LBB0_4
.LBB0_2:
mov byte ptr [rbp - 2], 0
jmp .LBB0_4
.LBB0_3:
mov al, byte ptr [rbp - 4]
test al, 1
jne .LBB0_7
jmp .LBB0_6
.LBB0_4:
mov al, byte ptr [rbp - 2]
and al, 1
movzx eax, al
pop rbp
ret
.LBB0_5:
mov byte ptr [rbp - 1], 1
jmp .LBB0_8
.LBB0_6:
mov byte ptr [rbp - 1], 0
jmp .LBB0_8
.LBB0_7:
mov al, byte ptr [rbp - 3]
test al, 1
jne .LBB0_5
jmp .LBB0_6
.LBB0_8:
test byte ptr [rbp - 1], 1
jne .LBB0_1
jmp .LBB0_2
我也尝试了 -O
选项,但输出为空(删除了未使用的函数)。
我故意不使用任何库以保持输出干净。请注意 clang
和 rustc
都使用 LLVM 作为后端。是什么解释了这种巨大的输出差异?如果只是禁用优化开关问题,我如何才能看到 rustc
?
的优化输出
在 Godbolt 中使用 -C opt-level=3
编译得到:
example::f1:
push rbp
mov rbp, rsp
xor dil, 1
or dil, sil
mov eax, edi
pop rbp
ret
看起来与 C++ 版本相当。有关更多说明,请参阅 。
注意:我必须使函数 pub extern
停止编译器将其优化为空,因为它未被使用。
使用编译器标志编译 -O
(), I get this output (Link to Godbolt):
push rbp
mov rbp, rsp
xor dil, 1
or dil, sil
mov eax, edi
pop rbp
ret
几件事:
为什么还是比C++版本长?
Rust 版本恰好长了三个指令:
push rbp
mov rbp, rsp
[...]
pop rbp
这些指令用于管理所谓的帧指针或 base 指针 (rbp
)。这主要是为了获得漂亮的堆栈跟踪。如果您通过 -fno-omit-frame-pointer
、you get the same result. Note that this uses g++
instead of clang++
since I haven't found a comparable option for the clang compiler.
为 C++ 版本禁用它
为什么 Rust 不省略帧指针?
事实上,确实如此。但是 Godbolt 向编译器添加了一个选项来保留帧指针。您可以阅读更多关于这样做的原因 here。如果您使用 rustc -O --crate-type=lib foo.rs --emit asm -C "llvm-args=-x86-asm-syntax=intel"
在本地编译代码,您将得到以下输出:
f1:
xor dil, 1
or dil, sil
mov eax, edi
ret
正是您的 C++ 版本的输出。
您可以 "undo" Godbolt 的功能 passing -C debuginfo=0
to the compiler。
为什么 -O
而不是 --release
?
Godbolt 直接使用 rustc
而不是 cargo
。 --release
标志是 cargo
的标志。要在 rustc
上启用优化,您需要传递 -O
或 -C opt-level=3
(或 0 到 3 之间的任何其他级别)。
要获得相同的 asm 代码,您需要禁用调试信息 - 这将删除帧指针推送。
-C opt-level=3 -C debuginfo=0
(https://godbolt.org/g/vdhB2f)
不是(实际差异比问题中显示的要小得多)。我很惊讶没有人检查 C++ 输出:
我写了一个简单的 C++ 函数来检查编译器优化:
bool f1(bool a, bool b) {
return !a || (a && b);
}
之后我检查了 Rust 中的等价物:
fn f1(a: bool, b: bool) -> bool {
!a || (a && b)
}
我使用 godbolt 检查汇编程序输出。
C++ 代码(由带有 -O3 标志的 clang 编译)的结果如下:
f1(bool, bool): # @f1(bool, bool)
xor dil, 1
or dil, sil
mov eax, edi
ret
而 Rust 等效的结果要长得多:
example::f1:
push rbp
mov rbp, rsp
mov al, sil
mov cl, dil
mov dl, cl
xor dl, -1
test dl, 1
mov byte ptr [rbp - 3], al
mov byte ptr [rbp - 4], cl
jne .LBB0_1
jmp .LBB0_3
.LBB0_1:
mov byte ptr [rbp - 2], 1
jmp .LBB0_4
.LBB0_2:
mov byte ptr [rbp - 2], 0
jmp .LBB0_4
.LBB0_3:
mov al, byte ptr [rbp - 4]
test al, 1
jne .LBB0_7
jmp .LBB0_6
.LBB0_4:
mov al, byte ptr [rbp - 2]
and al, 1
movzx eax, al
pop rbp
ret
.LBB0_5:
mov byte ptr [rbp - 1], 1
jmp .LBB0_8
.LBB0_6:
mov byte ptr [rbp - 1], 0
jmp .LBB0_8
.LBB0_7:
mov al, byte ptr [rbp - 3]
test al, 1
jne .LBB0_5
jmp .LBB0_6
.LBB0_8:
test byte ptr [rbp - 1], 1
jne .LBB0_1
jmp .LBB0_2
我也尝试了 -O
选项,但输出为空(删除了未使用的函数)。
我故意不使用任何库以保持输出干净。请注意 clang
和 rustc
都使用 LLVM 作为后端。是什么解释了这种巨大的输出差异?如果只是禁用优化开关问题,我如何才能看到 rustc
?
在 Godbolt 中使用 -C opt-level=3
编译得到:
example::f1:
push rbp
mov rbp, rsp
xor dil, 1
or dil, sil
mov eax, edi
pop rbp
ret
看起来与 C++ 版本相当。有关更多说明,请参阅
注意:我必须使函数 pub extern
停止编译器将其优化为空,因为它未被使用。
使用编译器标志编译 -O
(
push rbp
mov rbp, rsp
xor dil, 1
or dil, sil
mov eax, edi
pop rbp
ret
几件事:
为什么还是比C++版本长?
Rust 版本恰好长了三个指令:
push rbp mov rbp, rsp [...] pop rbp
这些指令用于管理所谓的帧指针或 base 指针 (
rbp
)。这主要是为了获得漂亮的堆栈跟踪。如果您通过-fno-omit-frame-pointer
、you get the same result. Note that this usesg++
instead ofclang++
since I haven't found a comparable option for the clang compiler. 为 C++ 版本禁用它
为什么 Rust 不省略帧指针?
事实上,确实如此。但是 Godbolt 向编译器添加了一个选项来保留帧指针。您可以阅读更多关于这样做的原因 here。如果您使用
rustc -O --crate-type=lib foo.rs --emit asm -C "llvm-args=-x86-asm-syntax=intel"
在本地编译代码,您将得到以下输出:f1: xor dil, 1 or dil, sil mov eax, edi ret
正是您的 C++ 版本的输出。
您可以 "undo" Godbolt 的功能 passing
-C debuginfo=0
to the compiler。为什么
-O
而不是--release
?Godbolt 直接使用
rustc
而不是cargo
。--release
标志是cargo
的标志。要在rustc
上启用优化,您需要传递-O
或-C opt-level=3
(或 0 到 3 之间的任何其他级别)。
要获得相同的 asm 代码,您需要禁用调试信息 - 这将删除帧指针推送。
-C opt-level=3 -C debuginfo=0
(https://godbolt.org/g/vdhB2f)
不是(实际差异比问题中显示的要小得多)。我很惊讶没有人检查 C++ 输出: