가장 좋아하는 비트 단위 기술은 무엇입니까? [닫은]


14

며칠 전, StackExchange 회원 Anto는 유효한 용도에 대해 문의했습니다. 비트 단위 연산자의 . 나는 정수에 2의 제곱을 곱하고 나누는 것보다 이동이 빠르다고 말했습니다. StackExchange 멤버 Daemin은 오른쪽 이동이 음수에 문제가 있다고 말하면서 반대했습니다.

그 시점에서 나는 부호있는 정수로 shift 연산자를 사용하는 것에 대해 생각하지 못했습니다. 주로이 기술을 저수준 소프트웨어 개발에 사용했습니다. 따라서 항상 부호없는 정수를 사용했습니다. C는 부호없는 정수에 대해 논리 이동을 수행합니다. 논리 시프트 권리를 수행 할 때는 부호 비트에주의를 기울이지 않습니다. 빈 비트는 0으로 채워집니다. 그러나 C는 부호있는 정수를 오른쪽으로 시프트 할 때 산술 시프트 연산을 수행합니다. 빈 비트는 부호 비트로 채워집니다. 이 차이로 인해 음수 값이 0으로 잘리는 대신 무한대로 반올림됩니다. 이는 부호있는 정수 나누기와는 다른 동작입니다.

몇 분의 생각으로 1 차 솔루션이 만들어졌습니다. 솔루션은 이동하기 전에 조건부로 음수 값을 양수 값으로 변환합니다. 시프트 연산이 수행 된 후 값이 조건부로 다시 음의 형태로 다시 변환됩니다.

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

이 솔루션의 문제점은 조건부 할당 명령문이 일반적으로 하나 이상의 점프 명령으로 변환되며 점프 명령이 두 명령 경로를 모두 디코딩하지 않는 프로세서에서 비쌀 수 있다는 것입니다. 명령 파이프 라인을 두 번 다시 프라이밍해야하는 경우 분할을 넘어서서 얻을 수있는 성능 향상에 좋은 찌그러짐이 생깁니다.

위에서 말했듯이 토요일에는 조건부 할당 문제에 대한 답변으로 일어났습니다. 산술 시프트 연산을 수행 할 때 발생하는 반올림 문제는 2의 보수 표현으로 작업 할 때만 발생합니다. 보수 표현으로는 발생하지 않습니다. 이 문제에 대한 해결책은 시프트 연산을 수행하기 전에 2의 ​​보수 값을 1의 보수 값으로 변환하는 것입니다. 그런 다음 1의 보수 값을 2의 보수 값으로 다시 변환해야합니다. 놀랍게도, 시프트 연산을 수행하기 전에 음의 값을 조건부로 변환하지 않고이 연산 세트를 수행 할 수 있습니다.

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

2의 보수 음수 값은 1을 빼서 1의 보수 음수 값으로 변환됩니다. 반대로, 1의 보수 음수 값은 1을 더하여 2의 보수 음수 값으로 변환됩니다. 위에 나열된 코드는 부호 비트가 2의 보수에서 1의 보수로 또는 그 반대로 변환하는 데 사용되기 때문에 작동합니다 . 음수 값만 부호 비트가 설정됩니다. 따라서 a 가 양수 이면 변수 부호 는 0과 같습니다 .

위에서 말한 것처럼 위의 것과 같은 다른 비트 해킹을 생각할 수 있습니까? 가장 좋아하는 비트 단위 핵은 무엇입니까? 저는 항상 새로운 성능 지향 비트 단위 핵을 찾고 있습니다.


3
이 질문과 귀하의 계정 이름-세상은 다시 말이됩니다 ...
JK

+1 관심을 끄는 질문은 다음과 같습니다
:)

또한 빠른 패리티 계산을 한 번 수행했습니다. 패리티는 전통적으로 루프가 필요하고 비트가 설정된 경우 카운트를 포함하기 때문에 약간의 고통이 있습니다. 패리티는 shift와 XOR을 사용하여 계산할 수 있으며, 루프와 점프를 피하면서 차례로 수행되는 많은 것들이 있습니다.
quick_now

2
이 기술들에 대한 전체 책이 있다는 것을 알고 있습니까?
-Hackers

예, 비트 작업에 전념하는 웹 사이트도 있습니다. URL을 잊어 버렸지 만 Google은 곧 URL을 설정합니다.
quick_now

답변:


23

나는 Gosper의 핵 (HAKMEM # 175)을 좋아하는데, 숫자를 가져 와서 같은 수의 비트 세트로 다음 숫자를 얻는 매우 교활한 방법입니다. 예를 들어 다음과 같이 k항목 조합을 생성하는 데 유용합니다 n.

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1. 그러나 이제부터는 주석이 없는 디버깅 세션에서 이것을 찾는 것에 대한 악몽이 생길 것 입니다.
nikie

@nikie, muahahahaha! (나는 프로젝트 오일러 문제와 같은 일에 이것을 사용하는 경향이 있습니다-나의 하루 일은 많은 조합론을 포함하지 않습니다).
피터 테일러

7

빠른 역 제곱 루트 방법은 내가 본 것을 제곱근의 역을 계산에 가장 기괴한 비트 수준의 기술을 사용합니다 :

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

빠른 sqrt도 훌륭합니다. Carmack은 가장 큰 코더 중 하나 인 것 같습니다.
BenjaminB

Wikipedia는 더 오래된 소스를 가지고 있습니다 (예 : beyond3d.com/content/articles/15
MSalters

0

런타임 라이브러리 호출에 의지하지 않고 3으로 나누기.

스택 오버플로에 대한 힌트 덕분에 3으로 나누면 다음과 같이 근사 할 수 있습니다.

X / 3 = [(x / 4) + (x / 12)]

그리고 X / 12는 (x / 4) / 3입니다. 여기에 재귀 요소가 갑자기 나타납니다.

또한 연주중인 숫자의 범위를 제한하면 필요한 반복 횟수를 제한 할 수 있습니다.

따라서 부호없는 정수 <2000의 경우 다음은 빠르고 간단한 / 3 알고리즘입니다. 숫자가 클수록 더 많은 단계를 추가하십시오. 컴파일러는 이것으로부터 지옥을 최적화하여 빠르고 작습니다.

정적 부호없는 short FastDivide3 (const 부호없는 short arg)
{
  부호없는 짧은 RunningSum;
  부호없는 짧은 FractionalTwelth;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  // 더 정확한 위의 두 줄 반복

  RunningSum을 반환합니다.
}

1
물론 이것은 매우 모호한 마이크로 컨트롤러에만 해당됩니다. 지난 20 년 동안 만들어진 실제 CPU는 정수 나누기를 위해 런타임 라이브러리가 필요하지 않습니다.
MSalters

1
물론 하드웨어 멀티 플라이어가없는 소형 마이크로는 실제로 매우 일반적입니다. 임베디드 토지에서 일하고 판매되는 백만 개의 제품 각각에 대해 $ 0.10를 절약하고 싶다면 더러운 속임수를 더 잘 아는 것이 좋습니다. 저축 된 돈 = 추가 이익으로 상사가 매우 행복합니다.
quick_now

음, 더러워 ... 그냥 .0101010101(약 1/3) 곱하면됩니다 . 전문가의 팁 : .000100010001and를 곱할 수도 있습니다 101(3 비트 시프트 만 필요하지만 근사치가 더 좋습니다).010101010101
MSalters

부동 소수점없이 정수 만 사용하여 어떻게 할 수 있습니까?
quick_now

1
비트 단위, x * 101 = x + x << 2. 마찬가지로, x * 0.000100010001은 x >> 4 + x >> 8 + x >> 12입니다.
MSalters

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.