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

며칠 전, 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과 같습니다 .

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



답변

나는 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;
}

답변

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

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;
}

답변

런타임 라이브러리 호출에 의지하지 않고 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을 반환합니다.
}

답변

Erlang을 살펴보면 비트 작업을 수행하기위한 전체 DSL이 있습니다. 따라서 다음과 같이 말하면 데이터 구조를 비트 단위로 나눌 수 있습니다.

<> = << 1,17,42 : 16 >>.

자세한 내용은 여기 : http://www.erlang.org/doc/reference_manual/expressions.html#id75782