[英]GCC x86-64 Suboptimal Assembly Output, why?
查看以下代碼的程序集輸出時(無優化,-O2和-O3產生非常相似的結果):
int main(int argc, char **argv)
{
volatile float f1 = 1.0f;
volatile float f2 = 2.0f;
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
海灣合作委員會做了一些我很難遵循的事情:
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
為什么GCC將浮點值移動到xmm0和xmm1兩次並且還運行兩次ucomiss?
做以下事情不是更快嗎?
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jb .L8 # jump if less than
je .L4 # jump if equal
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
我根本不是一個真正的匯編程序員,但對我來說,運行重復指令似乎很奇怪。 我的代碼版本有問題嗎?
更新
如果你刪除我最初使用的volatile並用scanf()替換它,你會得到相同的結果:
int main(int argc, char **argv)
{
float f1;
float f2;
scanf("%f", &f1);
scanf("%f", &f2);
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
和相應的匯編程序:
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
leaq -4(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
leaq -8(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC1, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC2, %edi
call puts
.L4:
movl $0, %eax
leave
ret
最后更新
在回顧了一些后續評論之后,似乎是漢(根據Jonathan Leffler的帖子評論過)解決了這個問題。 海灣合作委員會不進行優化不是因為它不能,而是因為我沒有告訴它。 似乎這一切都歸結為IEEE浮點規則並且處理嚴格的條件GCC不能簡單地在第一個UCOMISS之后跳轉或者在第一個UCOMISS之后跳轉,因為它需要處理浮點數的所有特殊條件。 當使用han對-ffast-math優化器的推薦時(沒有-Ox標志啟用-ffast-math,因為它可以破壞某些程序)GCC正是我正在尋找的東西:
使用GCC 4.3.2“gcc -S -O3 -ffast-math test.c”生成以下程序集。
.LC0:
.string "%f"
.LC1:
.string "+"
.LC2:
.string "-"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB25:
subq $24, %rsp
.LCFI0:
movl $.LC0, %edi
xorl %eax, %eax
leaq 20(%rsp), %rsi
call scanf
leaq 16(%rsp), %rsi
xorl %eax, %eax
movl $.LC0, %edi
call scanf
movss 20(%rsp), %xmm0
comiss 16(%rsp), %xmm0
ja .L11
jb .L12
xorl %eax, %eax
addq $24, %rsp
.p2align 4,,1
.p2align 3
ret
.p2align 4,,10
.p2align 3
.L12:
movl $.LC2, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
.p2align 4,,10
.p2align 3
.L11:
movl $.LC1, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
請注意,現在用一個COMISS直接替換兩個UCOMISS指令,然后是JA(如果上面是跳躍)和JB(如果下面跳轉)。 如果你讓它使用-ffast-math,GCC能夠確定這個優化!
UCOMISS vs COMISS(http://www.softeng.rl.ac.uk/st/archive/SoftEng/SESP/html/SoftwareTools/vtune/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc315。 htm):“UCOMISS指令與COMISS指令的不同之處在於它僅在源操作數是SNaN時發出無效的SIMD浮點異常信號。如果源操作數是QNaN或SNaN,則COMISS指令信號無效。”
再次感謝大家的有益討論。
這是另一個原因:如果仔細觀察它,它就不是同一個表達式。
它們不是彼此的補充。
因此,無論如何你必須進行兩次比較。
volatile
將強制重新加載值。
編輯:(見評論,我忘了你可以用旗幟做到這一點)
回答新問題:
從編譯器的角度來看,將這兩個ucomiss
組合起來並不是一個完全明顯的優化。
為了組合它們,編譯器必須:
ucomiss %xmm0, %xmm1
與ucomiss %xmm1, %xmm0
“相同”。 所有這些都需要在編譯器執行指令選擇后完成。 大多數優化過程都是在指令選擇之前完成的。
讓我更擔心的是,為什么f1
和f2
在你擺脫了volatiles
后沒有被保存在寄存器中。 -O3
真的給你這個嗎?
volatile
限定符意味着f1
和f2
的值可能會以編譯器無法檢測/期望的方式發生變化,因此每次使用f1
或f2
都必須訪問內存。 生成的代碼就是這樣 - 所以它是正確的。
如果從任一變量或兩個變量中刪除volatile
限定符,則與您獲得的代碼進行比較和對比。 最終,您可能需要從某處讀取f1
和f2
的值,以避免編譯器在編譯時評估表達式。
在更新的代碼中, ucomiss
指令有兩種不同的咒語,盡管前面的movss
指令是相同的:
ucomiss %xmm0, %xmm1
ucomiss %xmm1, %xmm0
對於反轉條件, ucomiss
指令的操作數的ucomiss
是相反的:
if (f1 > f2)
if (f1 < f2)
我不相信優化器正在盡可能優化,但問題是變形超出了我的專業水平。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.