2012-04-09 102 views
4

首先,原諒我,因爲我的問題可能看起來很愚蠢,但我很好奇爲什麼我在這個非常簡單的代碼中獲得了性能提升。內聯彙編性能優於C

這裏的彙編代碼:

__asm { 
    mov eax, 0 
    mov ecx, 0 
    jmp startloop 
    notequal: 
    inc eax 
    mov ecx, eax 
    sub ecx, 2 
    startloop: 
    cmp eax, 2000000000 
    jne notequal 
}; 

,這是C代碼:

long x = 0; 
long ii = 0; 
for(; ii < 2000000000; ++ii) 
{ 
    x = ii - 2; 
}; 

C代碼大約需要1060毫秒(在發行版本)來完成我的酷睿i5 2500K機和組裝上在780ms結束。速度增加了25%。我不明白爲什麼我會得到這個結果,因爲25%是一個很大的差異。編譯器不夠智能,無法生成我編寫的相同的彙編代碼?

BTW我使用MSVC 2010

感謝


這裏是一個的被MSVC

[email protected]: 
; Line 36 
    lea esi, DWORD PTR [eax-2] 
    inc eax 
    cmp eax, 2000000000    ; 77359400H 
    jl SHORT [email protected] 

什麼呢lea指令產生的(ASM)的代碼做在這種情況下?

更新2


非常感謝大家。我剛剛在Nehalem xeon CPU上測試了這個代碼,結果在這裏完全相同。看起來像一個未知的原因,在Sandy橋上,asm代碼運行得更快。

+7

......和你用什麼編譯選項?任何優化選項,還是編譯器生成最可能的最笨的代碼?另外,請編譯器生成它自己的程序集輸出並進行比較。 – 2012-04-09 23:06:58

+3

最佳優化:'ii = 2000000000,x = 1999999997'。如果您需要優化幫助,那麼帶有「禁忌」優化的代碼是不現實的。 – Dani 2012-04-09 23:10:01

+0

我使用了標準優化選項,/ O2和/ Ot – Davita 2012-04-09 23:10:22

回答

2

@ modelnine的評論是正確的 - 正在使用lea來簡化循環中的分配。您有:

x = ii - 2; 

而且lea(加載有效地址)指令被有效執行:

esi = &(*(eax - 2)); 

&*相互抵消(這是很重要的 - 在這種情況下,非關聯eax可能會導致問題),所以你得到:

esi = eax - 2; 

你的C代碼究竟是什麼試圖去做。

+0

唯一的麻煩是,編譯器生成的版本(用'lea')比較慢。我已經證實了這一點。 – Mysticial 2012-04-09 23:28:20

+0

那麼你必須自己寫或者向編譯器編寫者抱怨,我想。我將答案改爲「簡化」而不是「加速」。 – 2012-04-09 23:29:11

+5

我認爲這是一件硬件事情。在Nehalem,沒有區別。在桑迪橋上,有25%的差異。所以這可能是一個管道問題。與編譯器無關。 – Mysticial 2012-04-09 23:30:14

1

你爲什麼不嘗試gcc -Ofast也許gcc -O1

,這裏是一個玩笑話:gcc -Q -Ofast --help=optimizers,對從GNU手冊!

,這裏是一個比較:

section .text 
global _start 

_start: 
    mov eax, 0 
    mov ecx, 0 
    jmp startloop 
    notequal: 
    inc eax 
    mov ecx, eax 
    sub ecx, 2 
    startloop: 
    cmp eax, 2000000000 
    jne notequal 

    int  0x80 

    mov  ebx,0 
    mov  eax,1 
    int  0x80 

爲此我1.306ms和下其計時到:使用gcc -O1

real 0m0.001s 
user 0m0.000s 
sys  0m0.000s 

定時爲:

real 0m1.295s 
user 0m1.262s 
sys  0m0.006s 

這實際上執行代碼。

對於MSVC,應該能夠使用/ O2或/ O1編譯選項獲得類似的結果。使用鐺3.1

隨着優化

#include <iostream> 
#include <chrono> 

int main() { 
    auto start = std::chrono::high_resolution_clock::now(); 

    asm (R"(
     mov $0, %eax 
     mov $0, %ecx 
     jmp startloop 
     notequal: 
     inc %eax 
     mov %eax,%ecx 
     sub $2,%ecx 
     startloop: 
     cmp $2000000000,%eax 
     jne notequal 
    )"); 

    auto finish = std::chrono::high_resolution_clock::now(); 
    std::cout << (finish-start).count() << '\n'; 
} 

打開了ASM版了一下:這裏詳細http://msdn.microsoft.com/en-us/library/k1ack8f1.aspx

2

我比較了非ASM版本:

#include <iostream> 
#include <chrono> 

int main() { 
    auto start = std::chrono::high_resolution_clock::now(); 

    long x = 0; 
    long ii = 0; 
    for(; ii < 2000000000; ++ii) 
    { 
     x = ii - 2; 
    }; 

    auto finish = std::chrono::high_resolution_clock::now(); 
    std::cout << (finish-start).count() << '\n'; 
    std::cout << x << ii << '\n'; 
} 

與ASM版本1.4秒,而非ASM版本花費45納秒。這對於程序集版本來說慢了大約32百萬分之一。

這裏對非ASM版本生成的彙編:

movl $1999999997, %esi  ## imm = 0x773593FD 
callq __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEl 
movq %rax, %rdi 
movl $2000000000, %esi  ## imm = 0x77359400 
callq __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEl 
+1

它只需要45納秒,因爲整個循環被優化出來...... – Mysticial 2012-04-10 00:02:59

+4

@ g24l這是一個笑話。 Cla正在優化整個事情。有任何ASM顯示的唯一原因是因爲我打印出x和ii。 45 ns基本上只是調用定時功能所需的時間。 – bames53 2012-04-10 00:05:11

+1

@ bames53哎呀對不起,夥計,我的意思是寫,我也證實了這些結果,但我正在和我的女朋友通電話,這需要我自己的CPU時間的50%... – 2012-04-10 00:08:58