簡體   English   中英

如果(!boolvar){...在1個asm指令中可以做什么?

[英]possible to do if (!boolvar) { … in 1 asm instruction?

這個問題更多的是出於好奇而非必要性:

是否有可能重寫c代碼if ( !boolvar ) { ...以某種方式編譯為1 cpu指令?

我試過在理論層面考慮這個問題,這就是我想出的:

if ( !boolvar ) { ...

需要首先否定變量然后根據 - > 2指令(否定+分支)進行分支

if ( boolvar == false ) { ...

需要將false的值加載到寄存器中,然后根據 - > 2指令(加載+分支)進行分支

if ( boolvar != true ) { ...

需要將true的值加載到寄存器然后分支(“branch-if-not-equal”),具體取決於 - > 2條指令(load +“branch-if-not-equal”)

我的假設錯了嗎? 我有什么東西可以俯瞰嗎?

我知道我可以生成程序的中間asm版本,但我不知道如何以某種方式使用它,所以我可以一方面打開編譯器優化,同時沒有一個空的if語句優化掉(或者有if語句與其內容一起優化,給出一些非通用的答案)

PS:當然我也搜索谷歌和SO這個,但有這么短的搜索條件我真的找不到任何有用的東西

PPS:我對一個語義上等效的版本沒問題,這個版本不是語法等價的,例如不使用if


編輯:如果我對發出的asm指令的假設是錯誤的,請隨時糾正我。


編輯2:我實際上已經學習了大約15年前,並在大約5年前重新學習了alpha架構,但我希望我的問題仍然足夠明確,以弄清楚我在問什么。 此外,如果它有助於找到一個好的答案,你可以自由地假設消費者cpus中常見的任何類型的處理器擴展,直到AVX2(截至撰寫本文時的當前haswell cpu)。

在我的帖子結束時,它將說明為什么你不應該針對這種行為(在x86上)。

正如Jerry Coffin所寫,x86中的大多數跳轉都取決於標志寄存器。

但有一個例外:如果ecx / rcx寄存器為零,則跳轉的j*cxz指令集。 為此,您需要確保您的boolvar使用ecx寄存器。 您可以通過專門將其分配給該寄存器來實現

register int boolvar asm ("ecx");

但到目前為止,並非所有編譯器都使用j*cxz指令集。 icc有一個標志可以做到這一點,但通常不建議這樣做。 英特爾手冊說明了兩條指令

test ecx, ecx
jz ...

在處理器上更快。

這樣做的原因是x86是CISC(復雜)指令集。 在實際的硬件中,雖然處理器會將在asm中作為一條指令出現的復雜指令拆分成多個微指令,然后以RISC方式執行。 這就是為什么並非所有指令都需要相同的執行時間的原因,有時多個小指令比一個指令要快。

testjz是單個微指令,但是jecxz將被分解為那兩個。

存在j*cxz指令集的唯一原因是,如果要在不修改標志寄存器的情況下進行條件跳轉。

是的,這是可能的 - 但這樣做取決於此代碼發生的上下文。

x86中的條件分支取決於標志寄存器中的值。 為了編譯為單個指令,其他一些代碼將需要設置正確的標志,所以剩下的就是像jnz wherever一樣的單指令。

例如:

boolvar = x == y;
if (!boolvar) {
    do_something();
}

......可能會最終呈現為:

    mov eax, x
    cmp eax, y    ; `boolvar = x == y;`
    jz @f
    call do_something
@@:

根據您的觀點,它甚至可以編譯為僅指令的一部分。 例如,可以“預測”相當多的指令,因此只有在某些先前定義的條件為真時才執行它們。 在這種情況下,您可能有一條指令用於將“boolvar”設置為正確的值,后跟一條有條件地調用函數,因此沒有一條(完整)指令對應於if語句本身。

雖然你不太可能在體面的C語言中看到它,但是單個匯編語言指令可能包括更多。 舉一個明顯的例子,考慮如下:

    x = 10;
looptop:
    -- x;
    boolvar = x == 0;
    if (!boolvar)
        goto looptop;

整個序列可以編譯成類似於:

    mov ecx, 10
looptop:
    loop looptop

我的假設是錯的

你有幾個假設是錯誤的。 首先你應該知道1條指令不一定比多條指令快。 例如,在較新的μarchs中, test可以與jcc進行宏融合,因此2個指令將作為一個運行。 或者划分是如此之慢,以至於可能已經完成了數十或數百個更簡單的指令。 如果if塊比多條指令慢,那么將if塊編譯為單條指令是不值得的

此外, if ( !boolvar ) { ...不需要首先否定變量然后根據它進行分支 x86中的大多數跳轉都基於標志,它們同時具有yes和no條件,因此不需要否定該值。 我們可以簡單地跳到非零而不是跳零

類似地, if ( boolvar == false ) { ...不需要將false值加載到寄存器中,然后根據該值進行分支 false是一個等於0的常量,可以作為立即數嵌入到指令中(如cmp reg, 0 )。 但是為了檢查零,那么只需要一個簡單的test reg, reg就足夠了。 然后jnzjz將用於跳轉零/非零,這將與之前的test指令融合為一

可以創建一個編譯為單個指令的if標頭或主體,但它完全取決於您需要執行的操作以及使用的條件。 因為boolvar的標志可能已經可以從前一個語句中獲得,所以下一行中的if塊可以像Jerry Coffin的答案一樣直接跳轉

另外86具有有條件的移動,因此,如果內if是一個簡單的賦值那么它可能在1個指令來完成。 下面是一個示例及其輸出

int f(bool condition, int x, int y)
{
    int ret = x;
    if (!condition)
        ret = y;
    return ret;
}

f(bool, int, int):
        test    dil, dil ; if(!condition)
        mov     eax, edx ; ret = y
        cmovne  eax, esi ; if(condition) ret = x
        ret

在其他一些情況下,您甚至不需要有條件的移動或跳躍。 例如

bool f(bool condition)
{
    bool ret = false;
    if (!condition)
        ret = true;
    return ret;
}

編譯為單個xor而沒有任何跳轉

f(bool):
        mov     eax, edi
        xor     eax, 1
        ret

ARM體系結構(v7及更低版本)可以將任何指令作為條件運行,因此可以只轉換為一條指令

例如以下循環

while (i != j)
{
   if (i > j)
   {
       i -= j;
   }
   else
   {
       j -= i;
   }
}

可以轉換為ARM程序集

loop:   CMP  Ri, Rj         ; set condition "NE" if (i != j),
                            ;               "GT" if (i > j),
                            ;            or "LT" if (i < j)
        SUBGT  Ri, Ri, Rj   ; if "GT" (Greater Than), i = i-j;
        SUBLT  Rj, Rj, Ri   ; if "LT" (Less Than), j = j-i;
        BNE  loop           ; if "NE" (Not Equal), then loop

暫無
暫無

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

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