2012-04-09 106 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