어떤 결과가 나오든 0으로 나눗셈을 지원하는 가장 빠른 정수 나눗셈은 무엇입니까?
요약:.
가장 빨리 계산할 수 있는 방법을 찾고 있습니다.
(int) x / (int) y
y==0
대신, 나는 단지 임의적인 결과를 원한다.
배경:
이미지 처리 알고리즘을 코딩할 때 종종 (누적) 알파 값으로 나누어야 합니다.가장 간단한 변형은 정수 산술이 있는 플레인 C 코드입니다.는 보통 으로 나눗셈은 .alpha==0
, 문제가 않는 , 다, 다, 다, 다, 다, 다, 다, 다, 다 입니다.이치노alpha==0
.
세부사항:
다음과 같은 것을 찾고 있습니다.
result = (y==0)? 0 : x/y;
또는
result = x / MAX( y, 1 );
x와 y는 양의 정수입니다.네스트 루프에서는 코드가 여러 번 실행되기 때문에 조건부 분기를 제거할 방법을 찾고 있습니다.
y가 바이트 범위를 넘지 않는 경우 솔루션에 만족합니다.
unsigned char kill_zero_table[256] = { 1, 1, 2, 3, 4, 5, 6, 7, [...] 255 };
[...]
result = x / kill_zero_table[y];
하지만 이것은 분명히 더 큰 범위에서는 잘 작동하지 않는다.
마지막 질문은 다음과 같습니다.다른 모든 값은 변경하지 않고 0을 다른 정수 값으로 변경하는 가장 빠른 비트 트위들링 해킹은 무엇입니까?
설명
지점이 너무 비싸다고 100% 확신할 수는 없어요.다만, 다른 컴파일러를 사용하고 있기 때문에, 최적화가 거의 없는 벤치마킹을 선호합니다(이것은 확실히 의문입니다).
확실히 컴파일러는 비트의 트위들링에 관해서는 훌륭합니다만, 「상관없음」의 결과를 C로 표현할 수 없기 때문에, 컴파일러는 모든 범위의 최적화를 사용할 수 없습니다.
코드는 C와 완전히 호환되어야 합니다.메인 플랫폼은 Linux 64비트(gcc & clang 및 MacOS 탑재)입니다.
몇 가지 코멘트에 영감을 받아 펜티엄의 브랜치를 삭제하고gcc
int f (int x, int y)
{
y += y == 0;
return x/y;
}
컴파일러는 기본적으로 추가에서 테스트 조건 플래그를 사용할 수 있음을 인식합니다.
요청에 따라 어셈블리:
.globl f
.type f, @function
f:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
movl 12(%ebp), %edx
testl %edx, %edx
sete %al
addl %edx, %eax
movl 8(%ebp), %edx
movl %eax, %ecx
popl %ebp
movl %edx, %eax
sarl $31, %edx
idivl %ecx
ret
이게 굉장히 인기 있는 질문이었으니까 좀 더 자세히 말씀드리겠습니다.위의 예는 컴파일러가 인식하는 프로그래밍 숙어에 기초하고 있습니다.위의 경우 부울식을 적분연산에 사용하고 조건 플래그의 사용은 이를 위해 하드웨어에서 발명된다.일반적으로 플래그는 C에서 관용구를 통해서만 접근할 수 있습니다.그렇기 때문에 (인라인) 어셈블리에 의존하지 않고 C에서 휴대용 다중 정밀 정수 라이브러리를 만드는 것은 매우 어렵습니다.내 생각에 대부분의 괜찮은 컴파일러들은 위의 관용구를 이해할 것 같다.
위의 코멘트 중 일부에서도 언급되었듯이 브랜치를 피하는 또 다른 방법은 사전 실행입니다.따라서 저는 philipp의 첫 번째 코드와 제 코드를 가져와 ARM 아키텍처용 컴파일러와 GCC 컴파일러를 통해 실행했습니다.양쪽 컴파일러는 양쪽 코드 샘플의 브랜치를 회피합니다.
ARM 컴파일러를 사용하는 Philipp 버전:
f PROC
CMP r1,#0
BNE __aeabi_idivmod
MOVEQ r0,#0
BX lr
GCC를 사용한 Philip 버전:
f:
subs r3, r1, #0
str lr, [sp, #-4]!
moveq r0, r3
ldreq pc, [sp], #4
bl __divsi3
ldr pc, [sp], #4
ARM 컴파일러를 사용한 코드:
f PROC
RSBS r2,r1,#1
MOVCC r2,#0
ADD r1,r1,r2
B __aeabi_idivmod
GCC에서의 내 코드:
f:
str lr, [sp, #-4]!
cmp r1, #0
addeq r1, r1, #1
bl __divsi3
ldr pc, [sp], #4
때문에 에서는 분할 루틴에 이 하드웨어가 만, 「」의 테스트는 「」의 테스트입니다.y == 0
는 사전 정의된 실행을 통해 완전히 구현됩니다.
다음은 GCC 4.7.2를 사용하는 Windows의 구체적인 수치를 나타냅니다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int result = 0;
for (int n = -500000000; n != 500000000; n++)
{
int d = -1;
for (int i = 0; i != ITERATIONS; i++)
d &= rand();
#if CHECK == 0
if (d == 0) result++;
#elif CHECK == 1
result += n / d;
#elif CHECK == 2
result += n / (d + !d);
#elif CHECK == 3
result += d == 0 ? 0 : n / d;
#elif CHECK == 4
result += d == 0 ? 1 : n / d;
#elif CHECK == 5
if (d != 0) result += n / d;
#endif
}
printf("%u\n", result);
}
콜을 하고 있지 에 주의해 주세요.srand()
rand()
는 항상 똑같은 결과를 반환합니다., 「 」,-DCHECK=0
0만 세면 얼마나 자주 나타나는지 알 수 있습니다.
이제 다양한 방법으로 컴파일 및 타이밍을 설정합니다.
$ for it in 0 1 2 3 4 5; do for ch in 0 1 2 3 4 5; do gcc test.cc -o test -O -DITERATIONS=$it -DCHECK=$ch && { time=`time ./test`; echo "Iterations $it, check $ch: exit status $?, output $time"; }; done; done
에 표에 요약할 수 있는 출력을 나타냅니다.
Iterations → | 0 | 1 | 2 | 3 | 4 | 5
-------------+-------------------------------------------------------------------
Zeroes | 0 | 1 | 133173 | 1593376 | 135245875 | 373728555
Check 1 | 0m0.612s | - | - | - | - | -
Check 2 | 0m0.612s | 0m6.527s | 0m9.718s | 0m13.464s | 0m18.422s | 0m22.871s
Check 3 | 0m0.616s | 0m5.601s | 0m8.954s | 0m13.211s | 0m19.579s | 0m25.389s
Check 4 | 0m0.611s | 0m5.570s | 0m9.030s | 0m13.544s | 0m19.393s | 0m25.081s
Check 5 | 0m0.612s | 0m5.627s | 0m9.322s | 0m14.218s | 0m19.576s | 0m25.443s
경우 0은 으로 됩니다.-DCHECK=2
버전이 제대로 작동하지 않습니다.더 표시되기 하면 0은 0으로 됩니다.-DCHECK=2
이치노다른 옵션들 중에서, 사실 큰 차이는 없습니다.
★★★의 -O3
지만,, 른른른른른 른른른른른른른
Iterations → | 0 | 1 | 2 | 3 | 4 | 5
-------------+-------------------------------------------------------------------
Zeroes | 0 | 1 | 133173 | 1593376 | 135245875 | 373728555
Check 1 | 0m0.646s | - | - | - | - | -
Check 2 | 0m0.654s | 0m5.670s | 0m9.905s | 0m14.238s | 0m17.520s | 0m22.101s
Check 3 | 0m0.647s | 0m5.611s | 0m9.085s | 0m13.626s | 0m18.679s | 0m25.513s
Check 4 | 0m0.649s | 0m5.381s | 0m9.117s | 0m13.692s | 0m18.878s | 0m25.354s
Check 5 | 0m0.649s | 0m6.178s | 0m9.032s | 0m13.783s | 0m18.593s | 0m25.377s
이 경우 체크2는 다른 체크에 비해 단점이 없으며 제로(0)가 일반화되어도 이점을 유지합니다.
단, 컴파일러와 대표적인 샘플 데이터에 무슨 일이 일어나는지 실제로 측정해야 합니다.
플랫폼을 모르면 가장 효율적인 방법을 알 수 없습니다.다만, 범용 시스템에서는, 최적(인텔 어셈블러 구문 사용)에 가까운 경우가 있습니다.
)ecx
은 (배당금)으로 되어 있다.eax
)
mov ebx, ecx
neg ebx
sbb ebx, ebx
add ecx, ebx
div eax, ecx
분기되지 않은 단일 사이클 명령 4개와 분할.몫은 안에 있을 것이다.eax
그리고 나머지는 안에 있을 것이다.edx
(이러면 왜 컴파일러를 보내지 않으려 하는지 알 수 있습니다.)
이 링크에 따라 SIGFPE 신호를 차단할 수 있습니다.sigaction()
(제가 직접 사용해 본 적은 없지만, 효과가 있을 것이라고 생각합니다).
0으로 나누기 오류가 극히 드문 경우 가능한 가장 빠른 방법입니다. 즉, 유효한 분할이 아닌 0으로 나눗셈만 지불하면 일반 실행 경로는 변경되지 않습니다.
그러나 OS는 무시되는 모든 예외에 관여합니다.이것은 비용이 많이 듭니다.적어도 1000개의 좋은 나눗셈을 0으로 나누면 무시할 수 있습니다.예외가 더 자주 발생할 경우 분할 전에 모든 값을 확인하는 것보다 예외를 무시함으로써 더 많은 비용을 지불하게 될 수 있습니다.
언급URL : https://stackoverflow.com/questions/16777456/what-is-the-fastest-integer-division-supporting-division-by-zero-no-matter-what
'programing' 카테고리의 다른 글
Chrome에서 동일한 원본 정책 사용 안 함 (0) | 2022.10.08 |
---|---|
JSON 대신 생성된 HTML을 반환하는 것이 나쁜 관행인 이유는 무엇입니까?아니면 그러한가? (0) | 2022.10.08 |
Python의 Windows 경로 (0) | 2022.10.08 |
Windows 10에서 MariaDB에 대한 쿼리 로깅 (0) | 2022.10.08 |
OSX 10.6에서 Python 및 Django와 함께 MySQLdb를 사용하는 방법 (0) | 2022.10.08 |