0과 비교할 때 int 연산자! = 및 ==
! = 및 ==가 0 또는 0이 아닌 것을 테스트하는 가장 빠른 방법이 아니라는 것을 발견했습니다.
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에서 더 빠른 버전을 생성하지 않는 이유는 무엇입니까?
- 어셈블리 출력이 동일 할 때 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가 있습니다.
EDIT2 : VC ++ 11과 VC ++ 2010을 모두 설치 한 사람이 벤치마킹 코드를 실행하고 타이밍을 게시 할 수 있습니까? 실제로 VC ++ 11의 버그 일 수 있습니다.
편집 : 내 코드에 대한 OP의 어셈블리 목록을 보았습니다. 나는 이것이 VS2011 의 일반적인 버그 조차 의심합니다 . 이것은 단순히 OP 코드에 대한 특별한 경우의 버그 일 수 있습니다. 나는 clang 3.2, gcc 4.6.2 및 VS2010으로 OP의 코드를있는 그대로 실행했으며 모든 경우에 최대 차이 는 ~ 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()
<
, >
및 ||
연산자 를 사용하는 것은 분명히 더 비쌉니다. ne1()
그리고 ne3()
이는 사용 ==
하고 !=
, 각각 사업자를 terser와 동일합니다.
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:
그리고 clang으로 :
.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에 버그로 신고하는 것 입니다.
Note: I compiled them as C source since I don't think using the corresponding C++ compiler would make any significant change here.
This is a great question, but I think you've fallen victim to the compiler's dependency analysis.
The compiler only has to clear the high bits of eax
once, and they remain clear for the second version. The second version would have to pay the price to xor eax, eax
except that the compiler analysis proved it's been left cleared by the first version.
The second version is able to "cheat" by taking advantage of work the compiler did in the first version.
How are you measuring times? Is it "(version one, followed by version two) in a loop", or "(version one in a loop) followed by (version two in a loop)"?
Don't do both tests in the same program (instead recompile for each version), or if you do, test both "version A first" and "version B first" and see if whichever comes first is paying a penalty.
Illustration of the cheating:
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();
If timer2
duration is less than timer1
duration, we don't conclude that multiplying by 31 is faster than multiplying by 2. Instead, we realize that the compiler performed common subexpression analysis, and the code became:
timer1.start();
double common = sqrt(n + 37 * y + exp(z));
double x1 = 2 * common;
timer1.stop();
timer2.start();
double x2 = 31 * common;
timer2.stop();
And the only thing proved is that multiplying by 31 is faster than computing common
. Which is hardly surprising at all -- multiplication is far far faster than sqrt
and exp
.
참고URL : https://stackoverflow.com/questions/10838675/int-operators-and-when-comparing-to-zero
'IT박스' 카테고리의 다른 글
Web.Config 디버그 / 릴리스 (0) | 2020.10.19 |
---|---|
gson.toJson ()에서 StackOverflowError가 발생합니다. (0) | 2020.10.19 |
부트 스트랩에서 두 열 사이의 세로 구분선 (0) | 2020.10.19 |
Mongoose-exec 함수는 무엇을합니까? (0) | 2020.10.19 |
선사 시대 날짜에 Javascript에서 날짜를 사용하는 방법은 무엇입니까? (0) | 2020.10.19 |