알려진 값 세트를 사용하여 정수가 두 정수 사이에 있는지 판별하는 가장 빠른 방법


389

x >= start && x <= end정수가 두 정수 사이에 있는지 테스트하는 C 또는 C ++ 보다 빠른 방법이 있습니까?

업데이트 : 내 특정 플랫폼은 iOS입니다. 이것은 주어진 사각형에서 픽셀을 원으로 제한하는 상자 흐림 기능의 일부입니다.

업데이트 : 허용 된 답변을 시도한 후 정상적인 x >= start && x <= end방법으로 한 줄의 코드에서 속도가 크게 향상되었습니다 .

업데이트 : XCode의 어셈블러가 포함 된 이후 및 이전 코드는 다음과 같습니다.

새로운 길

// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)

Ltmp1313:
 ldr    r0, [sp, #176] @ 4-byte Reload
 ldr    r1, [sp, #164] @ 4-byte Reload
 ldr    r0, [r0]
 ldr    r1, [r1]
 sub.w  r0, r9, r0
 cmp    r0, r1
 blo    LBB44_30

옛날 방식

#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)

Ltmp1301:
 ldr    r1, [sp, #172] @ 4-byte Reload
 ldr    r1, [r1]
 cmp    r0, r1
 bls    LBB44_32
 mov    r6, r0
 b      LBB44_33
LBB44_32:
 ldr    r1, [sp, #188] @ 4-byte Reload
 adds   r6, r0, #1
Ltmp1302:
 ldr    r1, [r1]
 cmp    r0, r1
 bhs    LBB44_36

분기를 줄이거 나 없애는 것이 어떻게 그렇게 빠른 속도를 제공 할 수 있는지는 매우 놀랍습니다.


28
이것이 충분히 빠르지 않은 이유는 무엇입니까?
Matt Ball

90
누가 관심을 갖는지, 흥미로운 질문입니다. 도전을위한 도전입니다.
David는 Reinstate Monica라고 말합니다.

46
@SLaks 따라서 우리는 그러한 모든 질문을 맹목적으로 무시하고 "최적화자가하게 하시겠습니까?"라고 말해야합니다.
David는 Reinstate Monica라고 말합니다.

87
질문을하는 이유는 중요하지 않습니다. 대답이 ' 아니오'
tay10r

41
이것은 내 애플 리케이션 중 하나의 기능에 병목
jjxtra

답변:


527

하나의 비교 / 분기 로이 작업을 수행하는 오래된 트릭이 있습니다. 실제로 속도를 향상 시킬지 여부는 의문의 여지가 있지만, 그렇게해도 눈치 채지 못하거나 신경 쓰지 않아도되지만, 두 번의 비교만으로 시작하면 크게 개선 될 가능성은 거의 없습니다. 코드는 다음과 같습니다.

// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
//  upper-lower, simply add + 1 to upper-lower and use the < operator.
    if ((unsigned)(number-lower) <= (upper-lower))
        in_range(number);

전형적인 현대 컴퓨터 (즉, 2의 보수를 사용하는 것)를 사용하면 부호없는 것으로의 변환은 실제로는 전혀 문제가되지 않습니다. 동일한 비트를 보는 방식의 변화 일뿐입니다.

일반적인 경우 사전 계산할 수 있습니다. upper-lower (추정 된) 루프 외부에서 있으므로 일반적으로 큰 시간이 걸리지 않습니다. 분기 명령의 수를 줄이면서 분기 예측도 향상시킵니다 (일반적으로). 이 경우 숫자가 범위의 하단 아래 또는 상단에 있는지 여부에 관계없이 동일한 분기가 수행됩니다.

이것이 어떻게 작동하는지에 대한 기본 아이디어는 매우 간단합니다. 부호없는 숫자로 볼 때 음수는 양수로 시작한 것보다 큽니다.

실제로이 방법 number은 간격을 원점으로 변환 하고 number간격 [0, D]이 어디에 있는지 확인합니다 D = upper - lower. 경우 number아래 하한은 네거티브 상부 위에 결합하는 경우, 그리고 : 보다 크다D .


8
@ TomásBadan : 합리적인 머신에서 하나의 사이클이 될 것입니다. 비싼 것은 지점입니다.
Oliver Charlesworth

3
단락으로 인해 추가 분기가 수행됩니까? 이 경우 lower <= x & x <= upper(대신 lower <= x && x <= upper) 성능도 향상됩니까?
Markus Mayr 2016 년

6
@ AK4749, jxh :이 너겟만큼 멋지지만, 공감하는 것을 주저합니다. 불행히도 이것이 실제로 더 빠르다는 것을 제안하는 것은 없기 때문에 (누군가 결과 어셈블러와 프로파일 링 정보를 비교할 때까지). 우리 모두가 알고 있듯이 OP의 컴파일러는 단일 분기 opcode로 OP의 코드를 렌더링 할 수 있습니다.
Oliver Charlesworth

152
와!!! 결과적 으로이 특정 코드 줄에 대한 내 앱의 순서가 크게 향상되었습니다. 더 낮은 값을 사전 계산하여이 프로파일 링의 시간을 25 %에서 2 % 미만으로 줄였습니다. 병목 현상은 이제 더하기와 빼기 작업이지만 지금은 충분할 것이라고 생각합니다. :)
jjxtra

28
아, 이제 @PsychoDad가 질문을 업데이트했지만 이것이 왜 더 빠른지 분명합니다. 실제 코드는 컴파일러 멀리 단락을 최적화 할 수있는 이유는 상기 비교에 부작용을 갖는다.
Oliver Charlesworth

17

이러한 소규모로 코딩하기 위해 상당한 최적화를 수행 할 수있는 경우는 거의 없습니다. 코드를 더 높은 수준에서 관찰하고 수정하면 성능이 크게 향상됩니다. 범위 테스트의 필요성을 완전히 제거하거나 O (n ^ 2) 대신 O (n) 만 수행 할 수 있습니다. 불평등의 한 측면이 항상 암시되도록 테스트 순서를 다시 지정할 수 있습니다. 알고리즘이 이상적 임에도 불구하고이 코드의 범위가 천만 번 테스트되는 방식을보고이를 일괄 처리하고 SSE를 사용하여 여러 테스트를 병렬로 수행하는 방법을 찾으면 이득이 더 많이 올 것입니다.


16
downvotes에도 불구하고 나는 내 대답을지지합니다. 생성 된 어셈블리 (허용 된 답변에 대한 주석의 pastebin 링크 참조)는 픽셀 처리 함수의 내부 루프에 뭔가 끔찍합니다. 허용되는 대답은 깔끔한 트릭이지만 극적인 효과는 반복 당 분기의 일부를 제거 할 것으로 예상되는 것보다 훨씬 뛰어납니다. 약간의 2 차 효과가 지배적이며,이 한 번의 테스트를 통해 전체 프로세스를 최적화하려는 시도는 먼지에서 영리한 범위 비교의 이점을 남길 것으로 기대합니다.
벤 잭슨

17

동일한 데이터에 대해 몇 번의 테스트를 수행 할 것인지에 따라 다릅니다.

테스트를 한 번 수행하는 경우 알고리즘 속도를 높이는 의미있는 방법이 없을 수 있습니다.

매우 한정된 값 집합에 대해이 작업을 수행하는 경우 조회 테이블을 만들 수 있습니다. 인덱싱을 수행하는 데 비용이 더 많이들 수 있지만 전체 테이블을 캐시에 맞출 수 있으면 코드에서 모든 분기를 제거하여 속도를 높일 수 있습니다.

데이터의 조회 테이블은 128 ^ 3 = 2,097,152입니다. 세 가지 변수 중 하나를 제어 할 수 있으므로 start = N한 번에 모든 인스턴스를 고려 하면 작업 세트의 크기가 128^2 = 16432바이트로 줄어듦으로써 대부분의 최신 캐시에 잘 맞습니다.

분기없는 조회 테이블이 명백한 비교보다 충분히 빠른지 확인하려면 실제 코드를 벤치마킹해야합니다.


따라서 값, 시작 및 종료가 주어지면 일종의 조회를 저장하고 그 사이에 있는지 알려주는 BOOL이 포함됩니까?
jjxtra 2016 년

옳은. 3D 조회 테이블이 bool between[start][end][x]됩니다. 액세스 패턴이 어떤 모양인지 알면 (예 : x가 단조 증가하는 경우) 전체 테이블이 메모리에 맞지 않더라도 로컬 성을 유지하도록 테이블을 디자인 할 수 있습니다.
Andrew Prock 2016 년

이 방법을 시도하고 어떻게 진행되는지 살펴볼 수 있는지 살펴 보겠습니다. 점이 원 안에 있으면 비트가 설정되는 라인 당 비트 벡터로 작업을 계획하고 있습니다. 비트 마스킹에 비해 바이트 또는 int32보다 빠를 것이라고 생각하십니까?
jjxtra

2

이 답변은 승인 된 답변으로 수행 된 테스트에 대해보고하는 것입니다. 나는 정렬 된 임의의 정수의 큰 벡터에 대해 폐쇄 범위 테스트를 수행했으며 놀랍게도 (낮은 <= num & & num <= high)의 기본 방법은 실제로 위의 허용 된 답변보다 빠릅니다! 테스트는 HP Pavilion g6 (6GB 램이있는 AMD A6-3400APU)에서 수행되었습니다. 테스트에 사용되는 핵심 코드는 다음과 같습니다.

int num = rand();  // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();

int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (randVec[i - 1] <= num && num <= randVec[i])
        ++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;

위의 허용되는 답변 인 다음과 비교하십시오.

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

randVec은 정렬 된 벡터입니다. MaxNum의 모든 크기에 대해 첫 번째 방법은 내 컴퓨터의 두 번째 방법을 능가합니다!


1
내 데이터가 정렬되지 않고 테스트는 iPhone arm CPU에서 수행됩니다. 데이터와 CPU가 다른 결과는 다를 수 있습니다.
jjxtra

내 테스트에서 정렬은 상한이 하한보다 작지 않은지 확인하는 것이 었습니다.
rezeli

1
정렬 된 숫자는 분기 예측이 매우 안정적이며 전환 지점에서 몇 가지를 제외하고 모든 분기를 올바르게 수행한다는 것을 의미합니다. 분기없는 코드의 장점은 예측할 수없는 데이터에 대한 이러한 종류의 잘못된 예측을 제거한다는 것입니다.
Andreas Klebinger

0

모든 변수 범위 검사의 경우 :

if (x >= minx && x <= maxx) ...

비트 연산을 사용하는 것이 더 빠릅니다.

if ( ((x - minx) | (maxx - x)) >= 0) ...

이렇게하면 두 개의 분기가 하나로 줄어 듭니다.

타입 안전에 관심이 있다면 :

if ((int32_t)(((uint32_t)x - (uint32_t)minx) | ((uint32_t)maxx - (uint32_t)x)) > = 0) ...

더 많은 변수 범위 확인을 결합 할 수 있습니다.

if (( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0) ...

이렇게하면 4 개의 분기가 1로 줄어 듭니다.

그것은이다 빠른 3.4 배 GCC에서 이전보다 :

여기에 이미지 설명을 입력하십시오


-4

정수에 대해 비트 단위 연산을 수행 할 수 없습니까?

0에서 128 사이 여야하므로 8 번째 비트가 설정되면 (2 ^ 7) 128 이상입니다. 포괄적 인 비교를 원하기 때문에 에지 사례는 고통이 될 것입니다.


3
그는 x <= end어디에 있는지 알고 싶어한다 end <= 128. 아닙니다 x <= 128.
벤 Voigt

1
이 문장은 " 0과 128 사이 여야하므로 8 번째 비트가 설정되면 (2 ^ 7) 128 이상 "잘못되었습니다. 256 고려
해피 그린 아이 낮잠

1
그래, 나는 충분히 그렇게 생각하지 않았다. 죄송합니다.
icedwater 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.