int运算符!=和==比较时为零
我发现!=和==不是用于测试零或非零的最快方法。
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上,指令是相同的(第一个版本看起来与第二个版本完全相同),但第二个版本仍然更快。
编辑:我添加了基准代码。 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相当大。 我有一个单独的源文件中的zero1,zero2,nonZero1,nonZero2。
编辑2:可以同时安装VC ++ 11和VC ++ 2010的人运行基准测试代码并发布计时? 这可能确实是VC ++ 11中的一个错误。
编辑:看到我的代码OP的程序集列表。 我怀疑这是VS2011的一个常见错误。 这可能只是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_workspacetestsne.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_workspacetestsne.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处于测试阶段。 我会认为这是一个错误。 我的另外两个编译器gcc 4.6.2和clang 3.2的测试结果与O2
优化开关在我的Windows 7盒子上产生了完全相同的三个测试(我有)。 以下是一个总结:
$ 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
我的建议是将此文件作为Microsoft Connect的错误提交。
注意:我将它们编译为C源代码,因为我不认为使用相应的C ++编译器会在这里做出任何重大更改。
这是一个很好的问题,但我认为你已经成为编译器依赖分析的受害者。
编译器只需要清除一次eax
的高位,并且它们在第二个版本中保持清晰。 第二个版本将不得不支付价格给xor eax, eax
除了编译器分析证明它已被第一个版本清除。
第二个版本可以通过利用编译器在第一个版本中所做的工作来“欺骗”。
你如何衡量时间? 它是“(版本一,其次是版本二)在一个循环中”,或者“(循环中的版本一),然后是(循环中的版本二)”?
不要在同一个程序中进行两次测试(而是针对每个版本进行重新编译),或者如果是这样,则首先测试“版本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快。相反,我们意识到编译器执行了常见的子表达式分析,代码变为:
timer1.start();
double common = sqrt(n + 37 * y + exp(z));
double x1 = 2 * common;
timer1.stop();
timer2.start();
double x2 = 31 * common;
timer2.stop();
唯一证明的是乘以31比计算common
要快。 这并不令人惊讶 - 乘积比sqrt
和exp
要快得多。
上一篇: int operators != and == when comparing to zero
下一篇: How do I achieve the theoretical maximum of 4 FLOPs per cycle?