int运算符!=和==比较时为零
我发现!=和==不是testing零或非零的最快方法。
bool nonZero1 = integer != 0; xor eax, eax test ecx, ecx setne al bool nonZero2 = integer < 0 || integer > 0; test ecx, ecx setne al bool zero1 = integer == 0; xor eax, eax test ecx, ecx sete al bool zero2 = !(integer < 0 || integer > 0); test ecx, ecx sete al
编译器:VC ++ 11优化标志:/ O2 / GL / LTCG
这是x86-32的程序集输出。 这两种比较的第二个版本在x86-32和x86-64上都快了12%。 但是,在x86-64上,指令是相同的(第一个版本看起来和第二个版本完全一样),但是第二个版本仍然更快。
- 为什么编译器不能在x86-32上生成更快的版本?
- 为什么在assembly输出相同的情况下,x86-64上的第二个版本仍然更快?
编辑:我已经添加了基准代码。 ZERO:1544ms,1358ms NON_ZERO:1544ms,1358ms http://pastebin.com/m7ZSUrcP或http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP
注意:在单个源文件中编译这些函数可能不方便,因为main.asm相当大。 我有零1,零2,nonZero1,nonZero2在一个单独的源文件。
编辑2:可以同时安装VC ++ 11和VC ++ 2010的人运行基准testing代码并发布计时? 这可能确实是VC ++ 11中的一个bug。
编辑:看到我的代码OP的程序集。 我怀疑这个VS2011现在甚至是一个普通的bug 。 这可能只是OP代码的一个特例。 我运行OP的代码是叮当3.2,gcc 4.6.2和VS2010,在所有情况下最大的差异都在〜1%。
只需编译源文件,对我的ne.c
文件和/O2
和/GL
标志进行适当的修改。 这是来源
int ne1(int n) { return n != 0; } int ne2(int n) { return n < 0 || n > 0; } int ne3(int n) { return !(n == 0); } int main() { int p = ne1(rand()), q = ne2(rand()), r = ne3(rand());}
和相应的程序集:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 TITLE D:\llvm_workspace\tests\ne.c .686P .XMM include listing.inc .model flat INCLUDELIB OLDNAMES EXTRN @__security_check_cookie@4:PROC EXTRN _rand:PROC PUBLIC _ne3 ; Function compile flags: /Ogtpy ; COMDAT _ne3 _TEXT SEGMENT _n$ = 8 ; size = 4 _ne3 PROC ; COMDAT ; File d:\llvm_workspace\tests\ne.c ; Line 11 xor eax, eax cmp DWORD PTR _n$[esp-4], eax setne al ; Line 12 ret 0 _ne3 ENDP _TEXT ENDS PUBLIC _ne2 ; Function compile flags: /Ogtpy ; COMDAT _ne2 _TEXT SEGMENT _n$ = 8 ; size = 4 _ne2 PROC ; COMDAT ; Line 7 xor eax, eax cmp eax, DWORD PTR _n$[esp-4] sbb eax, eax neg eax ; Line 8 ret 0 _ne2 ENDP _TEXT ENDS PUBLIC _ne1 ; Function compile flags: /Ogtpy ; COMDAT _ne1 _TEXT SEGMENT _n$ = 8 ; size = 4 _ne1 PROC ; COMDAT ; Line 3 xor eax, eax cmp DWORD PTR _n$[esp-4], eax setne al ; Line 4 ret 0 _ne1 ENDP _TEXT ENDS PUBLIC _main ; Function compile flags: /Ogtpy ; COMDAT _main _TEXT SEGMENT _main PROC ; COMDAT ; Line 14 call _rand call _rand call _rand xor eax, eax ret 0 _main ENDP _TEXT ENDS END
ne2()
使用了<
, >
和||
运营商显然比较昂贵。 分别使用==
和!=
运算符的ne3()
ne1()
和ne3()
是更接近和相当的。
Visual Studio 2011 处于testing阶段 。 我会认为这是一个错误。 我的另外两个编译器gcc 4.6.2和clang 3.2与O2
优化开关的testing在我的Windows 7盒子上testing了三个testing(我有)。 这里有一个总结:
$ cat ne.c #include <stdbool.h> bool ne1(int n) { return n != 0; } bool ne2(int n) { return n < 0 || n > 0; } bool ne3(int n) { return !(n != 0); } int main() {}
产量与gcc:
_ne1: LFB0: .cfi_startproc movl 4(%esp), %eax testl %eax, %eax setne %al ret .cfi_endproc LFE0: .p2align 2,,3 .globl _ne2 .def _ne2; .scl 2; .type 32; .endef _ne2: LFB1: .cfi_startproc movl 4(%esp), %edx testl %edx, %edx setne %al ret .cfi_endproc LFE1: .p2align 2,,3 .globl _ne3 .def _ne3; .scl 2; .type 32; .endef _ne3: LFB2: .cfi_startproc movl 4(%esp), %ecx testl %ecx, %ecx sete %al ret .cfi_endproc LFE2: .def ___main; .scl 2; .type 32; .endef .section .text.startup,"x" .p2align 2,,3 .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB3: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp call ___main xorl %eax, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE3:
和叮当声:
.def _ne1; .scl 2; .type 32; .endef .text .globl _ne1 .align 16, 0x90 _ne1: cmpl $0, 4(%esp) setne %al movzbl %al, %eax ret .def _ne2; .scl 2; .type 32; .endef .globl _ne2 .align 16, 0x90 _ne2: cmpl $0, 4(%esp) setne %al movzbl %al, %eax ret .def _ne3; .scl 2; .type 32; .endef .globl _ne3 .align 16, 0x90 _ne3: cmpl $0, 4(%esp) sete %al movzbl %al, %eax ret .def _main; .scl 2; .type 32; .endef .globl _main .align 16, 0x90 _main: pushl %ebp movl %esp, %ebp calll ___main xorl %eax, %eax popl %ebp ret
我的build议是将此文件作为Microsoft Connect的一个错误。
注意:我将它们编译为C源代码,因为我不认为使用相应的C ++编译器会在这里做任何重大更改。
这是一个很好的问题,但是我认为你已经成为编译器依赖分析的受害者。
编译器只需要清除一次eax
的高位,并且对于第二个版本它们保持清晰。 第二个版本将不得不支付价格xor eax, eax
除了编译器分析certificate它已被第一个版本清除。
第二个版本能够利用编译器在第一个版本中所做的工作来“作弊”。
你如何衡量时间? 是循环中的“(版本1,后面是版本2)”还是“(循环中的版本1)”(循环中的版本2)“?
不要在同一个程序中进行两个testing(而是重新编译每个版本),或者如果你这样做,先testing“版本A”和“版本B”,然后看看哪个先到先处罚。
作弊的插图:
timer1.start(); double x1 = 2 * sqrt(n + 37 * y + exp(z)); timer1.stop(); timer2.start(); double x2 = 31 * sqrt(n + 37 * y + exp(z)); timer2.stop();
如果timer2
持续时间小于timer1
持续时间,我们不会得出结论:乘以31会比乘以2快。相反,我们意识到编译器执行了常见的子expression式分析,代码变成:
timer1.start(); double common = sqrt(n + 37 * y + exp(z)); double x1 = 2 * common; timer1.stop(); timer2.start(); double x2 = 31 * common; timer2.stop();
唯一certificate的是,乘以31比计算common
要快。 这并不奇怪 – 乘法比sqrt
和exp
要快得多。