[英]gcc C if statement optimization
gcc是否優化代碼,例如:
if(cond)
func1()
func2()
if(cond)
func3()
如果cond是常數? 此外,什么是學習C優化的良好資源? (因此,我不必再問像這樣的愚蠢問題)
是的,如果編譯器證明允許這樣做並且認為有價值,那么肯定會對其進行優化。
我嘗試了最新版本的gcc
, icc
和clang
,它們都將上述代碼有效地進行了優化:
if (cond) {
func1();
func2();
func3();
} else {
func2();
}
也就是說,他們復制了對func2()
的調用以啟用此優化。 這是來自gcc的典型編譯代碼示例:
cmp edi, 33
je .L7
xor eax, eax
jmp func2
.L7:
sub rsp, 8
xor eax, eax
call func1
xor eax, eax
call func2
xor eax, eax
add rsp, 8
jmp func3
當然,不能保證這樣的優化-編譯器可能會認為這樣做不值得,並且可能取決於特定於編譯器的試探法和編譯選項。 例如,當使用-Os
(與我上面使用的-O2
相反), gcc
不再重新排列它,而是按照您編寫時或多或少地使用兩條cmp
指令對其進行編譯:
cmp edi, 33
jne .L5
xor eax, eax
call func1
.L5:
xor eax, eax
call func2
cmp edi, 33
jne .L4
xor eax, eax
jmp func3
.L4:
ret
另一方面, clang
和icc
在復制call
同時繼續通過單個比較對其進行編譯。
您可以在Godbolt上玩所有這些。
通常是的。 公共子表達式在函數作用域中計算,因此如果if(x * x / y> z + z)出現兩次,x,y和z不變,則該表達式將僅計算一次。 但是,編譯器需要知道,例如,變量的地址尚未被獲取並傳遞給某個地方的子例程。
您可以通過查看匯編代碼來檢查編譯器在做什么。 我已經創建了一個可以使用的C程序,以確保它具有一定的可變性,因此優化器不僅可以不斷折疊所有內容。
#include <stdio.h>
void func1(const char input) {
printf("func1: %c\n", input);
}
void func2(const char input) {
printf("func2: %c\n", input);
}
void func3(const char input) {
printf("func3: %c\n", input);
}
int main() {
char input = (char)getchar();
if(input == 'Y') {
func1(input);
}
func2(input);
if(input == 'Y') {
func3(input);
}
}
您可以使用-S
生成程序集。
gcc -S test.c -o test.asm
請注意,這是沒有優化的。 您可以找到比較而不必閱讀太多匯編程序,而是查找89,它是Y
的ASCII十進制表示形式。
LCFI10:
subq $16, %rsp
call _getchar
movb %al, -1(%rbp)
cmpb $89, -1(%rbp)
jne L5
movsbl -1(%rbp), %eax
movl %eax, %edi
call _func1
L5:
movsbl -1(%rbp), %eax
movl %eax, %edi
call _func2
cmpb $89, -1(%rbp)
jne L6
movsbl -1(%rbp), %eax
movl %eax, %edi
call _func3
L6:
movl $0, %eax
leave
那是兩個if塊的相當死板的翻譯。 您會看到兩個cmpb $89, -1(%rbp)
調用,指示兩次比較。
現在進行了優化: gcc -S -O3 test.c -o test.asm
LCFI0:
call _getchar
cmpb $89, %al
je L10
leaq lC1(%rip), %rdi
movsbl %al, %esi
xorl %eax, %eax
call _printf
L7:
xorl %eax, %eax
addq $8, %rsp
LCFI1:
ret
L10:
LCFI2:
leaq lC0(%rip), %rdi
movl $89, %esi
xorl %eax, %eax
call _printf
movl $89, %esi
xorl %eax, %eax
leaq lC1(%rip), %rdi
call _printf
movl $89, %esi
xorl %eax, %eax
leaq lC2(%rip), %rdi
call _printf
jmp L7
現在只有一個cmpb $89, %al
,但是也有多個movl $89, %esi
。 lC0,lC1和lC2是與這三個函數相對應的字符串"func1: %c\\n"
, "func2: %c\\n"
和"func3: %c\\n"
。
為了進行比較,這是clang的功能。
Ltmp12:
.cfi_offset %rbx, -24
callq _getchar
movsbl %al, %ebx
movzbl %bl, %eax
cmpl $89, %eax
jne LBB3_2
## BB#1:
leaq L_.str(%rip), %rdi
xorl %eax, %eax
movl %ebx, %esi
callq _printf
leaq L_.str.1(%rip), %rdi
xorl %eax, %eax
movl %ebx, %esi
callq _printf
leaq L_.str.2(%rip), %rdi
jmp LBB3_3
LBB3_2:
leaq L_.str.1(%rip), %rdi
與gcc一樣,現在只有一個比較。 並且,類似地,L_.str,L_.str.1和L.str.2分別是func1,func2和func3中的字符串。
實質上,兩者都將代碼更改為此。
if( input == 'Y' ) {
printf("func1: %c\n", input);
printf("func2: %c\n", input);
printf("func3: %c\n", input);
}
else {
printf("func2: %c\n", input);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.