簡體   English   中英

GCC x86-64裝配輸出次優,為什么?

[英]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組合起來並不是一個完全明顯的優化。

為了組合它們,編譯器必須:

  1. 認識到ucomiss %xmm0, %xmm1ucomiss %xmm1, %xmm0 “相同”。
  2. 然后它必須執行一個公共的子表達式消除傳遞來將其拉出來。

所有這些都需要在編譯器執行指令選擇完成。 大多數優化過程都是在指令選擇之前完成的。

讓我更擔心的是,為什么f1f2在你擺脫了volatiles后沒有被保存在寄存器中。 -O3真的給你這個嗎?

volatile限定符意味着f1f2的值可能會以編譯器無法檢測/期望的方式發生變化,因此每次使用f1f2都必須訪問內存。 生成的代碼就是這樣 - 所以它是正確的。

如果從任一變量或兩個變量中刪除volatile限定符,則與您獲得的代碼進行比較和對比。 最終,您可能需要從某處讀取f1f2的值,以避免編譯器在編譯時評估表達式。


在更新的代碼中, ucomiss指令有兩種不同的咒語,盡管前面的movss指令是相同的:

    ucomiss %xmm0, %xmm1
    ucomiss %xmm1, %xmm0

對於反轉條件, ucomiss指令的操作數的ucomiss是相反的:

if (f1 > f2)
if (f1 < f2)

我不相信優化器正在盡可能優化,但問題是變形超出了我的專業水平。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM