가 if( a < 901 )
보다 더 빨리 if( a <= 900 )
.
이 간단한 예제와 정확히 같지는 않지만 루프 복잡한 코드에서 약간의 성능 변화가 있습니다. 나는 이것이 사실 일 경우에 생성 된 머신 코드로 무언가를해야한다고 생각한다.
답변
아니요, 대부분의 아키텍처에서 더 빠르지는 않습니다. 지정하지 않았지만 x86에서 모든 적분 비교는 일반적으로 두 개의 기계 명령어로 구현됩니다.
test
또는cmp
명령어 세트EFLAGS
- 그리고 비교 유형 및 코드 레이아웃에 따라
Jcc
(점프) 명령 :jne
-같지 않으면 점프->ZF = 0
jz
-0이면 점프 (같음)->ZF = 1
jg
-더 큰 경우 점프->ZF = 0 and SF = OF
- (기타…)
예제 (간결하게 편집)$ gcc -m32 -S -masm=intel test.c
if (a < b) {
// Do something 1
}
컴파일 :
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jge .L2 ; jump if a is >= b
; Do something 1
.L2:
과
if (a <= b) {
// Do something 2
}
컴파일 :
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jg .L5 ; jump if a is > b
; Do something 2
.L5:
따라서 둘 사이의 유일한 차이점은 명령 jg
대 jge
명령입니다. 두 사람은 같은 시간이 걸립니다.
다른 점프 명령이 같은 시간이 걸린다는 것을 나타내는 것은 아무것도 없습니다. 이것은 대답하기가 약간 까다 롭지 만 여기에 내가 줄 수있는 것이 있습니다 .Intel Instruction Set Reference 에서는 모두 하나의 공통 명령 Jcc
(모두 조건이 충족되면 점프)으로 그룹화됩니다 . 부록 C. 지연 시간 및 처리량 의 최적화 참조 매뉴얼에 동일한 그룹화가 함께 이루어집니다 .
지연 시간 — 실행 코어가 명령을 구성하는 모든 μops의 실행을 완료하는 데 필요한 클럭주기 수입니다.
처리량 — 문제 포트가 동일한 명령을 다시 허용하기 전에 대기하는 데 필요한 클럭주기 수입니다. 많은 명령어의 경우 명령어의 처리량이 지연 시간보다 훨씬 적을 수 있습니다.
값 Jcc
은 다음과 같습니다.
Latency Throughput
Jcc N/A 0.5
다음 각주와 함께 Jcc
:
7) 조건부 점프 명령의 선택은 분기의 예측 성을 향상시키기 위해 3.4.1 절.“지점 예측 최적화”섹션의 권장 사항을 기반으로해야합니다. 분기가 성공적으로 예측되면 대기 시간
jcc
은 사실상 0입니다.
따라서 인텔 문서에서는 Jcc
어떤 명령도 다른 명령과 다르게 처리되지 않습니다 .
명령어를 구현하기 위해 사용 된 실제 회로에 대해 생각한다면 EFLAGS
, 조건이 충족되는지 여부를 결정하기 위해 서로 다른 비트에 간단한 AND / OR 게이트가 있다고 가정 할 수 있습니다 . 따라서 2 비트를 테스트하는 명령어가 1 개만 테스트하는 것보다 더 많거나 적은 시간이 걸리는 이유는 없습니다 (클럭주기보다 훨씬 짧은 게이트 전파 지연 무시).
편집 : 부동 소수점
이것은 x87 부동 소수점에도 적용됩니다. (위와 거의 동일하지만 double
대신 int
.
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
fstp st(0)
seta al ; Set al if above (CF=0 and ZF=0).
test al, al
je .L2
; Do something 1
.L2:
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; (same thing as above)
fstp st(0)
setae al ; Set al if above or equal (CF=0).
test al, al
je .L5
; Do something 2
.L5:
leave
ret
답변
역사적으로 (우리는 1980 년대와 1990 년대 초반을 얘기하고), 거기 몇몇 이 사실있는 아키텍처. 근본적인 문제는 정수 비교가 본질적으로 정수 빼기를 통해 구현된다는 것입니다. 이로 인해 다음과 같은 경우가 발생합니다.
Comparison Subtraction
---------- -----------
A < B --> A - B < 0
A = B --> A - B = 0
A > B --> A - B > 0
이제 A < B
뺄셈이 고 비트를 빌려야 뺄 때 , 뺄셈이 정확 해지려면, 손으로 더하거나 뺄 때와 같이 빌리십시오. 이 “빌려온”비트는 일반적으로 캐리 비트 라고 하며 분기 명령으로 테스트 할 수 있습니다. 빼기가 같음을 의미하는 동일 제로인 경우 0 비트라는 두 번째 비트 가 설정됩니다.
일반적으로 최소 두 개의 조건부 분기 명령어가있었습니다. 하나는 캐리 비트에서 분기하고 다른 하나는 0 비트에서 분기했습니다.
이제 문제의 핵심을 파악하기 위해 캐리 및 제로 비트 결과를 포함하도록 이전 표를 확장 해 보겠습니다.
Comparison Subtraction Carry Bit Zero Bit
---------- ----------- --------- --------
A < B --> A - B < 0 0 0
A = B --> A - B = 0 1 1
A > B --> A - B > 0 1 0
따라서이 경우 에만A < B
캐리 비트가 명확하기 때문에 분기에 대한 구현은 하나의 명령으로 수행 할 수 있습니다 .
;; Implementation of "if (A < B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; Branch if Carry is Zero to the new address
그러나 동등하지 않은 비교를 원한다면 평등의 경우를 잡기 위해 제로 플래그를 추가로 확인해야합니다.
;; Implementation of "if (A <= B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; branch if A < B
bzs address ;; also, Branch if the Zero bit is Set
따라서 일부 기계에서는 “보다 작음”비교를 사용 하면 하나의 기계 명령 이 저장 될 수 있습니다 . 이는 메가 헤르츠 미만 프로세서 속도와 1 : 1 CPU-to-memory 속도 비율과 관련이 있었지만 오늘날에는 거의 관련이 없습니다.
답변
내부 정수 유형에 대해 이야기하고 있다고 가정하면 하나가 다른 정수보다 빠를 수있는 방법은 없습니다. 그것들은 의미 상 분명히 동일합니다. 둘 다 컴파일러에게 정확히 동일한 작업을 수행하도록 요청합니다. 끔찍하게 고장난 컴파일러 만이 이들 중 하나에 대해 열등한 코드를 생성합니다.
일부 플랫폼 있었다면 <
보다 더 빨리이었다 <=
간단한 정수 유형의 컴파일러한다 항상 변환 <=
에 <
상수. 나쁜 컴파일러가 아닌 모든 컴파일러 (플랫폼 용).
답변
둘 다 빠르지 않다는 것을 알았습니다. 컴파일러는 각 조건에서 다른 값으로 동일한 기계 코드를 생성합니다.
if(a < 901)
cmpl $900, -4(%rbp)
jg .L2
if(a <=901)
cmpl $901, -4(%rbp)
jg .L3
내 예 if
는 Linux의 x86_64 플랫폼에있는 GCC입니다.
컴파일러 작가는 꽤 똑똑한 사람들이며, 이러한 것들과 우리 대부분의 다른 사람들이 당연하다고 생각합니다.
상수가 아닌 경우 두 경우 모두 동일한 머신 코드가 생성됩니다.
int b;
if(a < b)
cmpl -4(%rbp), %eax
jge .L2
if(a <=b)
cmpl -4(%rbp), %eax
jg .L3
답변
부동 소수점 코드의 경우 <= 비교는 현대 아키텍처에서도 실제로 (한 명령으로) 느려질 수 있습니다. 첫 번째 기능은 다음과 같습니다.
int compare_strict(double a, double b) { return a < b; }
PowerPC에서 먼저 부동 소수점 비교 ( cr
조건 레지스터 업데이트 )를 수행 한 다음 조건 레지스터를 GPR로 이동하고 “보다 작은 비교”비트를 제자리로 이동 한 다음 리턴합니다. 네 가지 지침이 필요합니다.
이제이 기능을 대신 고려하십시오.
int compare_loose(double a, double b) { return a <= b; }
이를 위해서는 compare_strict
위와 동일한 작업이 필요 하지만 이제 “보다 작음”과 “같음”이라는 두 가지 관심 대상이 있습니다. 이 cror
두 비트를 하나로 결합 하려면 추가 명령어 ( -조건 레지스터 비트 OR)가 필요합니다 . 따라서 compare_loose
5 개의 지침이 compare_strict
필요하지만 4 개의 지침이 필요합니다.
컴파일러가 다음과 같이 두 번째 함수를 최적화 할 수 있다고 생각할 수 있습니다.
int compare_loose(double a, double b) { return ! (a > b); }
그러나 이것은 NaN을 잘못 처리합니다. NaN1 <= NaN2
그리고 NaN1 > NaN2
모두 필요가 false로 평가합니다.
답변
아마도 그 이름이없는 책의 저자는 읽은 a > 0
것보다 더 빨리 읽히고 그것이 a >= 1
보편적으로 사실이라고 생각했을 것입니다.
그러나는 0
( CMP
아키텍처에 따라, 예를 들어로 대체 될 수 있기 때문에)가 포함되어 OR
있기 때문이 아니라 때문입니다 <
.
답변
적어도 이것이 사실이라면 컴파일러는 <= b를! (a> b)로 사소하게 최적화 할 수 있으므로 비교 자체가 실제로 느리더라도 가장 순수한 컴파일러를 제외하고는 차이를 느끼지 못할 것입니다 .