설정된 최하위 비트의 위치


120

정수로 설정된 최하위 비트의 위치를 ​​결정하는 효율적인 방법을 찾고 있습니다. 예를 들어 0x0FF0의 경우 4가됩니다.

사소한 구현은 다음과 같습니다.

unsigned GetLowestBitPos(unsigned value)
{
   assert(value != 0); // handled separately

   unsigned pos = 0;
   while (!(value & 1))
   {
      value >>= 1;
      ++pos;
   }
   return pos;
}

그것에서 일부 사이클을 짜내는 방법에 대한 아이디어가 있습니까?

(참고 :이 질문은 사람들이 xyzoptimization이 나쁘다고 말하는 것이 아니라 그러한 것을 즐기는 사람들을위한 것입니다.)

[편집] 아이디어 주셔서 감사합니다 모두! 나는 다른 몇 가지도 배웠다. 멋있는!


while ((값 _N >> (++ pos))! = 0);
Thomas

답변:


170

Bit Twiddling Hacks 는 성능 / 최적화 논의가 첨부 된 우수한 비트 트위들 링 해킹 모음을 제공합니다. 귀하의 문제 (해당 사이트에서)에 대한 제가 가장 좋아하는 솔루션은«곱하기 및 조회»입니다.

unsigned int v;  // find the number of trailing zeros in 32-bit v 
int r;           // result goes here
static const int MultiplyDeBruijnBitPosition[32] = 
{
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

유용한 참조 :


18
왜 반대 투표입니까? 이것은 곱셈 속도에 따라 가장 빠른 구현 일 수 있습니다. 확실히 코드는 간결하며 (v & -v) 트릭은 모든 사람이 배우고 기억해야하는 것입니다.
Adam Davis

2
+1 매우 멋지다. if (X & Y) 연산에 비해 곱하기 연산이 얼마나 비쌉니까?
Brian R. Bondy

4
아무도 이것의 성능이 __builtin_ffsl또는 비교되는 방법을 알고 ffsl있습니까?
Steven Lu

2
@Jim Balter, 그러나 모듈로는 현대 하드웨어의 곱셈에 비해 매우 느립니다. 그래서 나는 그것을 더 나은 해결책이라고 부르지 않을 것입니다.
연역적

2
0x01 값과 0x00 값 모두 배열에서 값 0이되는 것 같습니다. 분명히이 트릭은 0이 전달되면 가장 낮은 비트가 설정됨을 나타냅니다!
abelenky

80

내장 된 ffs를 사용하지 않는 이유는 무엇 입니까? (저는 Linux에서 man 페이지를 가져 왔지만 그보다 더 널리 사용 가능합니다.)

ffs (3)-Linux man 페이지

이름

ffs-단어에서 첫 번째 비트 세트 찾기

개요

#include <strings.h>
int ffs(int i);
#define _GNU_SOURCE
#include <string.h>
int ffsl(long int i);
int ffsll(long long int i);

기술

ffs () 함수는 단어 i에 설정된 첫 번째 (최하위) 비트의 위치를 ​​반환합니다. 최하위 비트는 위치 1이고 최상위 위치 (예 : 32 또는 64)입니다. ffsll () 및 ffsl () 함수는 동일한 작업을 수행하지만 크기가 다른 인수를 사용합니다.

반환 값

이 함수는 첫 번째 비트 세트의 위치를 ​​반환하거나 i에 비트가 설정되지 않은 경우 0을 반환합니다.

준수

4.3BSD, POSIX.1-2001.

노트

BSD 시스템은 <string.h>.


6
참고로, 가능한 경우 해당 어셈블리 명령으로 컴파일됩니다.
Jérémie

46

이를 수행하는 x86 어셈블리 명령 ( bsf)이 있습니다. :)

더 최적화?!

참고 :

이 수준에서의 최적화는 본질적으로 아키텍처에 따라 다릅니다. 오늘날의 프로세서는 너무 복잡하여 (분기 예측, 캐시 미스, 파이프 라이닝 측면에서) 어떤 코드가 어떤 아키텍처에서 더 빨리 실행되는지 예측하기가 너무 어렵습니다. 작업을 32에서 9로 줄이면 일부 아키텍처에서는 성능이 저하 될 수도 있습니다. 단일 아키텍처에서 최적화 된 코드는 다른 아키텍처에서 더 나쁜 코드를 초래할 수 있습니다. 특정 CPU에 대해이를 최적화하거나 그대로두고 컴파일러가 더 낫다고 생각하는 것을 선택하도록 할 것이라고 생각합니다.


20
@dwc : 이해합니다.하지만이 절을 생각합니다. "일부주기를 짜내는 방법이 있나요?" 그런 대답을 완벽하게 받아 들일 수 있습니다!
Mehrdad Afshari

5
+1 그의 대답은 엔디 안성 때문에 반드시 그의 아키텍처에 따라 달라 지므로 어셈블리 지침으로 내려가는 것은 완벽하게 유효한 대답입니다.
Chris Lutz

3
+1 영리한 대답, 예, C 또는 C ++는 아니지만 작업에 적합한 도구입니다.
Andrew Hare

1
잠깐만 요. 정수의 실제 값은 여기서 중요하지 않습니다. 죄송합니다.
Chris Lutz

2
@Bastian : 피연산자가 0이면 ZF = 1로 설정합니다.
Mehrdad Afshari

43

대부분의 최신 아키텍처에는 가장 낮은 세트 비트 또는 가장 높은 세트 비트의 위치를 ​​찾거나 선행 0의 수를 계산하는 등의 지침이 있습니다.

이 클래스의 명령어가 하나라도 있으면 다른 명령어를 저렴하게 에뮬레이션 할 수 있습니다.

잠시 시간을내어 종이로 작업 x & (x-1)하고 x에서 가장 낮은 세트 비트를 지우고 ( x & ~(x-1) )구조, 단어 길이 등에 관계없이 가장 낮은 세트 비트 만 반환 할 것임을 깨달으십시오 .이 사실을 알면 하드웨어 카운트 리더를 사용하는 것은 간단합니다. -zeroes / high-set-bit는 명시적인 명령이없는 경우 가장 낮은 세트 비트를 찾습니다.

관련 하드웨어 지원이 전혀없는 경우 여기에 제공된 카운트 선행 0의 곱셈 및 조회 구현 또는 Bit Twiddling Hacks 페이지 에있는 것 중 하나 는 위의 ID를 사용하여 가장 낮은 세트 비트를 제공하도록 간단 하게 변환 할 수 있습니다. 분기가 없다는 장점이 있습니다.


18

Weee, 많은 솔루션이 있지만 벤치 마크가 아닙니다. 당신은 사람들이 당신 자신을 부끄러워해야합니다 ;-)

내 컴퓨터는 Windows 7 64 비트를 실행하는 Intel i530 (2.9GHz)입니다. 32 비트 버전의 MinGW로 컴파일했습니다.

$ gcc --version
gcc.exe (GCC) 4.7.2

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2
$ bench
Naive loop.         Time = 2.91  (Original questioner)
De Bruijn multiply. Time = 1.16  (Tykhyy)
Lookup table.       Time = 0.36  (Andrew Grant)
FFS instruction.    Time = 0.90  (ephemient)
Branch free mask.   Time = 3.48  (Dan / Jim Balter)
Double hack.        Time = 3.41  (DocMax)

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2 -march=native
$ bench
Naive loop.         Time = 2.92
De Bruijn multiply. Time = 0.47
Lookup table.       Time = 0.35
FFS instruction.    Time = 0.68
Branch free mask.   Time = 3.49
Double hack.        Time = 0.92

내 코드 :

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


#define ARRAY_SIZE 65536
#define NUM_ITERS 5000  // Number of times to process array


int find_first_bits_naive_loop(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            if (value == 0)
                continue;
            unsigned pos = 0;
            while (!(value & 1))
            {
                value >>= 1;
                ++pos;
            }
            total += pos + 1;
        }
    }

    return total;
}


int find_first_bits_de_bruijn(unsigned nums[ARRAY_SIZE])
{
    static const int MultiplyDeBruijnBitPosition[32] = 
    {
       1, 2, 29, 3, 30, 15, 25, 4, 31, 23, 21, 16, 26, 18, 5, 9, 
       32, 28, 14, 24, 22, 20, 17, 8, 27, 13, 19, 7, 12, 6, 11, 10
    };

    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int c = nums[i];
            total += MultiplyDeBruijnBitPosition[((unsigned)((c & -c) * 0x077CB531U)) >> 27];
        }
    }

    return total;
}


unsigned char lowestBitTable[256];
int get_lowest_set_bit(unsigned num) {
    unsigned mask = 1;
    for (int cnt = 1; cnt <= 32; cnt++, mask <<= 1) {
        if (num & mask) {
            return cnt;
        }
    }

    return 0;
}
int find_first_bits_lookup_table(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int value = nums[i];
            // note that order to check indices will depend whether you are on a big 
            // or little endian machine. This is for little-endian
            unsigned char *bytes = (unsigned char *)&value;
            if (bytes[0])
                total += lowestBitTable[bytes[0]];
            else if (bytes[1])
              total += lowestBitTable[bytes[1]] + 8;
            else if (bytes[2])
              total += lowestBitTable[bytes[2]] + 16;
            else
              total += lowestBitTable[bytes[3]] + 24;
        }
    }

    return total;
}


int find_first_bits_ffs_instruction(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            total +=  __builtin_ffs(nums[i]);
        }
    }

    return total;
}


int find_first_bits_branch_free_mask(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            int i16 = !(value & 0xffff) << 4;
            value >>= i16;

            int i8 = !(value & 0xff) << 3;
            value >>= i8;

            int i4 = !(value & 0xf) << 2;
            value >>= i4;

            int i2 = !(value & 0x3) << 1;
            value >>= i2;

            int i1 = !(value & 0x1);

            int i0 = (value >> i1) & 1? 0 : -32;

            total += i16 + i8 + i4 + i2 + i1 + i0 + 1;
        }
    }

    return total;
}


int find_first_bits_double_hack(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            double d = value ^ (value - !!value); 
            total += (((int*)&d)[1]>>20)-1022; 
        }
    }

    return total;
}


int main() {
    unsigned nums[ARRAY_SIZE];
    for (int i = 0; i < ARRAY_SIZE; i++) {
        nums[i] = rand() + (rand() << 15);
    }

    for (int i = 0; i < 256; i++) {
        lowestBitTable[i] = get_lowest_set_bit(i);
    }


    clock_t start_time, end_time;
    int result;

    start_time = clock();
    result = find_first_bits_naive_loop(nums);
    end_time = clock();
    printf("Naive loop.         Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_de_bruijn(nums);
    end_time = clock();
    printf("De Bruijn multiply. Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_lookup_table(nums);
    end_time = clock();
    printf("Lookup table.       Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_ffs_instruction(nums);
    end_time = clock();
    printf("FFS instruction.    Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_branch_free_mask(nums);
    end_time = clock();
    printf("Branch free mask.   Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_double_hack(nums);
    end_time = clock();
    printf("Double hack.        Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
}

8
de Bruijn과 lookup에 대한 벤치 마크는 오해의 소지가있을 수 있습니다. 이와 같이 빡빡한 루프에 앉아 있으면 첫 번째 작업 후 각 유형에 대한 룩업 테이블이 마지막 루프가 끝날 때까지 L1 캐시에 고정됩니다. 이것은 실제 사용과 일치하지 않을 것입니다.
MattW

1
낮은 바이트에 0이있는 입력의 경우 포인터 캐스트로 인해 이동하는 대신 저장 / 다시로드하여 더 높은 바이트를 가져옵니다. (완전히 불필요한 BTW, 시프트와는 달리 엔디안 의존성). 어쨌든, 마이크로 벤치 마크는 핫 캐시로 인해 비현실적 일뿐만 아니라 분기 예측자가 준비되어 있고 매우 잘 예측하고 LUT가 덜 작동하도록하는 입력을 테스트합니다. 많은 실제 사용 사례는 입력이 아닌 결과의 더 균일 한 분포를 갖습니다.
Peter Cordes

2
FFS 루프는 안타깝게도 BSF 명령어의 잘못된 종속성으로 인해 느려집니다. 예전 컴파일러는 피할 수 없습니다 ( 그러나 최신 gcc는 popcnt / lzcnt / tzcnt에 대해 동일해야합니다 . BSF실제 동작 때문에 출력에 잘못된 종속성이 있습니다. 입력 = 0 일 때 출력을 변경하지 않고 그대로 둡니다.) gcc는 불행히도 루프 반복 사이에 레지스터를 지우지 않음으로써 루프 전달 종속성으로 전환합니다. 따라서 루프는 BSF (3) + CMOV에서 병목 현상이 발생한 5주기 당 1 회로 실행되어야합니다. (2) 대기 시간.
피터 코르

1
귀하의 벤치 마크는 LUT가 FFS 방법의 처리량의 거의 정확히 두 배를 가지고 있다는 것을 발견했습니다. 이는 정적 분석 예측과 매우 잘 일치합니다. 루프의 유일한 직렬 종속성이 총계에 합산되기 때문에 지연 시간이 아니라 처리량을 측정합니다. 잘못된 종속성이 없으면 ffs()클럭 당 1 개의 처리량을 가져야합니다 (3 uops, BSF 용 1 개, CMOV 용 2 개, 다른 포트에서 실행 가능). 동일한 루프 오버 헤드로 CPU에서 클럭 당 3 개로 실행할 수있는 것은 7 개의 ALU uop입니다. 오버 헤드가 지배합니다! 출처 : agner.org/optimize
피터 코르

1
예, 비 순차적 실행은 기다려야하는 입력으로 bsf ecx, [ebx+edx*4]처리하지 않으면 루프의 여러 반복과 겹칠 수 ecx있습니다. (ECX는 이전 iteraton의 CMOV가 마지막으로 작성했습니다). 그러나 CPU는 "소스가 0이면 수정되지 않은 상태로두기"동작을 구현하기 위해 그런 방식으로 동작합니다 (따라서 TZCNT의 경우와 같은 실제 거짓 dep가 아닙니다. 가정에 분기 + 추측 실행이 없기 때문에 데이터 종속성이 필요합니다. 입력이 0이 아님). 우리는 추가하여 극복 할 수 xor ecx,ecx전과를 bsfECX에 대한 종속성을 깰.
Peter Cordes

17

이에 대한 가장 빠른 (비 내장 / 비 어셈블러) 솔루션은 가장 낮은 바이트를 찾은 다음 256 항목 조회 테이블에서 해당 바이트를 사용하는 것입니다. 이것은 4 개의 조건부 명령어와 최상의 경우 1의 최악의 경우 성능을 제공합니다. 이것은 가장 적은 양의 명령어 일뿐만 아니라 최신 하드웨어에서 매우 중요한 분기의 양이 가장 적다는 것입니다.

테이블 (256 개의 8 비트 항목)에는 0-255 범위의 각 숫자에 대한 LSB의 인덱스가 포함되어야합니다. 값의 각 바이트를 확인하고 0이 아닌 가장 낮은 바이트를 찾은 다음이 값을 사용하여 실제 인덱스를 조회합니다.

256 바이트의 메모리가 필요하지만이 기능의 속도가 너무 중요하다면 256 바이트가 그만한 가치가 있습니다.

byte lowestBitTable[256] = {
.... // left as an exercise for the reader to generate
};

unsigned GetLowestBitPos(unsigned value)
{
  // note that order to check indices will depend whether you are on a big 
  // or little endian machine. This is for little-endian
  byte* bytes = (byte*)value;
  if (bytes[0])
    return lowestBitTable[bytes[0]];
  else if (bytes[1])
      return lowestBitTable[bytes[1]] + 8;
  else if (bytes[2])
      return lowestBitTable[bytes[2]] + 16;
  else
      return lowestBitTable[bytes[3]] + 24;  
}

1
실제로 세 가지 조건문 중 최악의 경우입니다 :)하지만 예, 이것은 가장 빠른 접근 방식입니다 (일반적으로 사람들이 이와 같은 인터뷰 질문에서 찾고있는 것).
Brian

4
어딘가에 +8, +16, +24를 원하지 않습니까?
Mark Ransom

7
모든 조회 테이블은 캐시 미스 가능성을 높이고 명령을 실행하는 것보다 몇 배 더 높은 메모리 액세스 비용을 초래할 수 있습니다.
Mehrdad Afshari

1
나는 심지어 비트 시프트를 사용할 것입니다 (매번 8 씩 이동). 그러면 레지스터를 사용하여 전적으로 수행 할 수 있습니다. 포인터를 사용하면 메모리에 액세스해야합니다.
Johannes Schaub-litb

1
합리적인 솔루션이지만 캐시에없는 조회 테이블 (지시 된대로 해결할 수 있음)과 분기 수 (잠재적 인 분기 예측 오류) 사이에서 곱하기 및 조회 솔루션 (분기 없음, 더 작은 조회 테이블). 물론 내장 또는 인라인 어셈블리를 사용할 수 있다면 더 나은 선택 일 것입니다. 그래도이 솔루션은 나쁘지 않습니다.

13

OMG는 이것이 방금 나선형을 이루었습니다.

이러한 예제 중 대부분이 부족한 것은 모든 하드웨어가 어떻게 작동하는지에 대한 약간의 이해입니다.

분기가있을 때마다 CPU는 어떤 분기를 사용할지 추측해야합니다. 명령 파이프에는 추측 된 경로를 안내하는 명령이로드됩니다. CPU가 잘못 추측 한 경우 명령 파이프가 플러시되고 다른 분기를로드해야합니다.

상단의 간단한 while 루프를 고려하십시오. 추측은 루프 내에 머무르는 것입니다. 루프를 떠날 때 적어도 한 번은 잘못됩니다. 이것은 명령 파이프를 플러시합니다. 이 동작은 루프를 떠날 것이라고 추측하는 것보다 약간 낫습니다.이 경우 모든 반복에서 명령 파이프를 플러시합니다.

손실되는 CPU주기의 양은 프로세서 유형에 따라 크게 다릅니다. 그러나 20 ~ 150 개의 CPU 손실주기를 예상 할 수 있습니다.

다음으로 더 나쁜 그룹은 값을 작은 조각으로 나누고 몇 개의 분기를 더 추가하여 몇 번의 반복을 절약 할 것이라고 생각하는 곳입니다. 이러한 각 분기는 명령 파이프를 플러시 할 수있는 추가 기회를 추가하고 추가로 20 ~ 150 클럭 사이클을 소모합니다.

테이블에서 값을 조회 할 때 어떤 일이 발생하는지 생각해 보겠습니다. 적어도 함수가 처음 호출 될 때는 값이 현재 캐시에 없을 가능성이 있습니다. 이는 값이 캐시에서로드되는 동안 CPU가 중단됨을 의미합니다. 다시 말하지만 이것은 기계마다 다릅니다. 새로운 Intel 칩은 실제로 이것을 현재 스레드가 캐시로드가 완료되기를 기다리는 동안 스레드를 교환 할 수있는 기회로 사용합니다. 이것은 명령 파이프 플러시보다 비용이 많이들 수 있지만이 작업을 여러 번 수행하는 경우 한 번만 발생할 가능성이 높습니다.

분명히 가장 빠른 상수 시간 솔루션은 결정 론적 수학을 포함하는 솔루션입니다. 순수하고 우아한 솔루션.

이것이 이미 다루어 졌다면 사과드립니다.

XCODE AFAIK를 제외하고 내가 사용하는 모든 컴파일러에는 순방향 비트 스캔과 역방향 비트 스캔 모두에 대한 컴파일러 내장 함수가 있습니다. 이들은 대부분의 하드웨어에서 캐시 미스, 분기 미스 예측 및 다른 프로그래머 생성 걸림돌이없는 단일 어셈블리 명령어로 컴파일됩니다.

Microsoft 컴파일러의 경우 _BitScanForward 및 _BitScanReverse를 사용합니다.
GCC의 경우 __builtin_ffs, __builtin_clz, __builtin_ctz를 사용하십시오.

또한 논의중인 주제에 대해 충분히 알지 못하는 경우 답변을 게시하거나 잠재적으로 오해의 소지가있는 신규 사용자를 게시하지 마십시오.

솔루션을 제공하는 것을 완전히 잊어 버렸습니다. 이것은 작업에 대한 어셈블리 수준 명령이없는 IPAD에서 사용하는 코드입니다.

unsigned BitScanLow_BranchFree(unsigned value)
{
    bool bwl = (value & 0x0000ffff) == 0;
    unsigned I1 = (bwl * 15);
    value = (value >> I1) & 0x0000ffff;

    bool bbl = (value & 0x00ff00ff) == 0;
    unsigned I2 = (bbl * 7);
    value = (value >> I2) & 0x00ff00ff;

    bool bnl = (value & 0x0f0f0f0f) == 0;
    unsigned I3 = (bnl * 3);
    value = (value >> I3) & 0x0f0f0f0f;

    bool bsl = (value & 0x33333333) == 0;
    unsigned I4 = (bsl * 1);
    value = (value >> I4) & 0x33333333;

    unsigned result = value + I1 + I2 + I3 + I4 - 1;

    return result;
}

여기서 이해해야 할 것은 비용이 많이 드는 것은 비교가 아니라 비교 후에 발생하는 분기라는 것입니다. 이 경우 비교는 .. == 0과 함께 0 또는 1의 값으로 강제되며 그 결과는 분기의 양쪽에서 발생했을 수있는 수학을 결합하는 데 사용됩니다.

편집하다:

위의 코드는 완전히 손상되었습니다. 이 코드는 작동하며 여전히 분기가 없습니다 (최적화 된 경우).

int BitScanLow_BranchFree(ui value)
{
    int i16 = !(value & 0xffff) << 4;
    value >>= i16;

    int i8 = !(value & 0xff) << 3;
    value >>= i8;

    int i4 = !(value & 0xf) << 2;
    value >>= i4;

    int i2 = !(value & 0x3) << 1;
    value >>= i2;

    int i1 = !(value & 0x1);

    int i0 = (value >> i1) & 1? 0 : -32;

    return i16 + i8 + i4 + i2 + i1 + i0;
}

0이 주어지면 -1을 반환합니다. 0에 대해 신경 쓰지 않거나 0에 대해 31을 얻고 싶다면 i0 계산을 제거하여 시간을 절약하십시오.


3
내가 고쳤어. 게시 한 내용을 테스트하십시오.
Jim Balter 2013 년

5
삼항 연산자를 포함하는 경우 어떻게 "분기없는"이라고 부를 수 있습니까?
BoltBait

2
조건부 이동입니다. 가능한 두 값을 매개 변수로 사용하고 조건 평가에 따라 mov 작업을 수행하는 단일 어셈블리 언어 명령어입니다. 따라서 "Branch Free"입니다. 다른 알 수 없거나 잘못된 주소로 이동하지 않습니다.
Dan


7

세트 비트 검색 관련된 이 유사한 게시물에서 영감을 받아 다음을 제공합니다.

unsigned GetLowestBitPos(unsigned value)
{
   double d = value ^ (value - !!value); 
   return (((int*)&d)[1]>>20)-1023; 
}

장점 :

  • 루프 없음
  • 분기 없음
  • 일정한 시간에 실행
  • 경계를 벗어난 결과를 반환하여 value = 0을 처리합니다.
  • 단 두 줄의 코드

단점 :

  • 코딩 된대로 엔디안이 거의 없다고 가정합니다 (상수를 변경하여 수정할 수 있음).
  • double이 실수 * 8 IEEE float (IEEE 754)라고 가정합니다.

업데이트 : 주석에서 지적했듯이 공용체는 더 깨끗한 구현이며 (적어도 C의 경우) 다음과 같이 보입니다.

unsigned GetLowestBitPos(unsigned value)
{
    union {
        int i[2];
        double d;
    } temp = { .d = value ^ (value - !!value) };
    return (temp.i[1] >> 20) - 1023;
}

이것은 모든 것에 대한 little-endian 스토리지가있는 32 비트 int를 가정합니다 (x86 프로세서를 생각해보십시오).


1
흥미 롭습니다. 저는 여전히 비트 산술에 복식을 사용하는 것이
두렵지 만

frexp와 ()를 사용하면 좀 더 휴대용 만들 수도
aka.nice

1
포인터 캐스팅에 의한 타입 -punning은 C 또는 C ++에서 안전하지 않습니다. C ++에서 memcpy를 사용하거나 C에서 공용체를 사용하십시오. (또는 컴파일러가 안전하다고 보장하는 경우 C ++의 공용체를 사용하십시오. 예를 들어 C ++에 대한 GNU 확장 (많은 컴파일러에서 지원됨)은 공용체 유형 실행이 안전함을 보장합니다.)
Peter 코르

1
이전 gcc는 또한 포인터 캐스트 대신 공용체를 사용하여 더 나은 코드를 만듭니다. 저장 / 다시로드하는 대신 FP reg (xmm0)에서 rax (movq 사용)로 직접 이동합니다. 최신 gcc 및 clang은 두 가지 방법으로 movq를 사용합니다. 유니온 버전 은 godbolt.org/g/x7JBiL 을 참조하십시오 . 20 씩 산술 시프트를하는 것이 의도적입니까? 귀하의 가정이 목록도해야 int이며 int32_t, 그 서명 오른쪽 시프트는 (그것의 구현 정의 C ++에서) 산술 변화입니다
피터 코르

1
또한 BTW, Visual Studio (최소한 2013 년)는 test / setcc / sub 접근 방식을 사용합니다. 나는 cmp / adc를 더 좋아합니다.
DocMax

5

최악의 경우 32 개 미만의 작업으로 수행 할 수 있습니다.

원칙 : 2 비트 이상을 확인하는 것은 1 비트를 확인하는 것만 큼 효율적입니다.

예를 들어 어떤 그룹이 먼저 있는지 확인한 다음 해당 그룹에서 가장 작은 것에서 가장 큰 것까지 각 비트를 확인하는 것을 막을 수 없습니다.

그래서 ...
한 번에 2 비트를 확인하면 최악의 경우 (Nbits / 2) + 1 개의 확인이 있습니다.
한 번에 3 비트를 확인하면 최악의 경우 (Nbits / 3) + 총 2 번 확인합니다.
...

최적은 4 개의 그룹을 확인하는 것입니다. 최악의 경우 32 개 대신 11 개의 작업이 필요합니다.

이 그룹화 아이디어를 사용하는 경우 알고리즘의 1 회 검사에서 2 회 검사로 가장 좋은 경우가 있습니다. 그러나 최상의 경우에 추가 1 확인은 최악의 경우 절약을 위해 그만한 가치가 있습니다.

참고 : 루프를 사용하는 대신 전체를 작성하는 것이 더 효율적이기 때문입니다.

int getLowestBitPos(unsigned int value)
{
    //Group 1: Bits 0-3
    if(value&0xf)
    {
        if(value&0x1)
            return 0;
        else if(value&0x2)
            return 1;
        else if(value&0x4)
            return 2;
        else
            return 3;
    }

    //Group 2: Bits 4-7
    if(value&0xf0)
    {
        if(value&0x10)
            return 4;
        else if(value&0x20)
            return 5;
        else if(value&0x40)
            return 6;
        else
            return 7;
    }

    //Group 3: Bits 8-11
    if(value&0xf00)
    {
        if(value&0x100)
            return 8;
        else if(value&0x200)
            return 9;
        else if(value&0x400)
            return 10;
        else
            return 11;
    }

    //Group 4: Bits 12-15
    if(value&0xf000)
    {
        if(value&0x1000)
            return 12;
        else if(value&0x2000)
            return 13;
        else if(value&0x4000)
            return 14;
        else
            return 15;
    }

    //Group 5: Bits 16-19
    if(value&0xf0000)
    {
        if(value&0x10000)
            return 16;
        else if(value&0x20000)
            return 17;
        else if(value&0x40000)
            return 18;
        else
            return 19;
    }

    //Group 6: Bits 20-23
    if(value&0xf00000)
    {
        if(value&0x100000)
            return 20;
        else if(value&0x200000)
            return 21;
        else if(value&0x400000)
            return 22;
        else
            return 23;
    }

    //Group 7: Bits 24-27
    if(value&0xf000000)
    {
        if(value&0x1000000)
            return 24;
        else if(value&0x2000000)
            return 25;
        else if(value&0x4000000)
            return 26;
        else
            return 27;
    }

    //Group 8: Bits 28-31
    if(value&0xf0000000)
    {
        if(value&0x10000000)
            return 28;
        else if(value&0x20000000)
            return 29;
        else if(value&0x40000000)
            return 30;
        else
            return 31;
    }

    return -1;
}

나에게서 +1. 그것은 가장 빠른 아니지만 그것은 ... 빠른 점이었다 원래보다의
앤드류 그랜트

@ onebyone.livejournal.com : 코드에 버그가 있더라도 그룹화의 개념은 제가 전달하고자하는 포인트였습니다. 실제 코드 샘플은 그다지 중요하지 않으며 더 간결하게 만들 수 있지만 효율성은 떨어집니다.
Brian R. Bondy

나는 내 대답에 정말 나쁜 부분이 있는지 궁금하거나 사람들이 그것을 좋아하지 않았는지 전체적으로 썼는지 궁금합니다.
Brian R. Bondy

@ onebyone.livejournal.com : 두 개의 알고리즘을 비교할 때, 하나가 최적화 단계에 의해 마술처럼 변형 될 것이라고 가정하지 말고있는 그대로 비교해야합니다. 나는 내 알고리즘이 "더 빠르다"고 주장한 적이 없다. 더 적은 작업입니다.
Brian R. Bondy

@ onebyone.livejournal.com : ... 작업이 적다는 것을 알기 위해 위의 코드를 프로파일 링 할 필요가 없습니다. 분명히 알 수 있습니다. 프로파일 링이 필요한 주장을 한 적이 없습니다.
Brian R. Bondy

4

바이너리 검색을 사용하지 않는 이유는 무엇 입니까? 이는 항상 5 번의 작업 후에 완료됩니다 (정수 크기가 4 바이트라고 가정).

if (0x0000FFFF & value) {
    if (0x000000FF & value) {
        if (0x0000000F & value) {
            if (0x00000003 & value) {
                if (0x00000001 & value) {
                    return 1;
                } else {
                    return 2;
                }
            } else {
                if (0x0000004 & value) {
                    return 3;
                } else {
                    return 4;
                }
            }
        } else { ...
    } else { ...
} else { ...

+1 이것은 내 대답과 매우 유사합니다. 최상의 실행 시간은 내 제안보다 나쁘지만 최악의 실행 시간은 더 좋습니다.
Brian R. Bondy

2

또 다른 방법 (모듈러스 분할 및 조회)은 @ anton-tykhyy가 제공 하는 동일한 링크 에서 여기에 특별히 언급 할 가치가 있습니다. 이 방법은 성능면에서 DeBruijn 곱하기 및 조회 방법과 매우 유사하지만 약간의 차이가 있습니다.

계수 분할 및 조회

 unsigned int v;  // find the number of trailing zeros in v
    int r;           // put the result in r
    static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
    {
      32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
      7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
      20, 8, 19, 18
    };
    r = Mod37BitPosition[(-v & v) % 37];

모듈러스 분할 및 조회 방법은 v = 0x00000000 및 v = FFFFFFFF에 대해 서로 다른 값을 반환하는 반면 DeBruijn 곱하기 및 조회 방법은 두 입력 모두에서 0을 반환합니다.

테스트:-

unsigned int n1=0x00000000, n2=0xFFFFFFFF;

MultiplyDeBruijnBitPosition[((unsigned int )((n1 & -n1) * 0x077CB531U)) >> 27]); /* returns 0 */
MultiplyDeBruijnBitPosition[((unsigned int )((n2 & -n2) * 0x077CB531U)) >> 27]); /* returns 0 */
Mod37BitPosition[(((-(n1) & (n1))) % 37)]); /* returns 32 */
Mod37BitPosition[(((-(n2) & (n2))) % 37)]); /* returns 0 */

1
mod느립니다. 대신, 원래의 곱셈 - 및 - 조회 방법을 사용하고 뺄 수 !v에서 r가장자리의 경우를 처리 할 수 있습니다.
Eitan T

3
@EitanT 옵티 마이저 잘 해커 '기쁨에 같은 고속 증식로 그 모드를 변환 할 수 있습니다
phuclv

2

Chess Programming BitScan 페이지 에 따르면 빼고 XOR, 내 자신의 측정은 빨리 무효화 이상과 마스크입니다.

(에서 후행 0을 세는 경우 0, 내가 가지고있는 메서드는 반환 63되는 반면 부정 및 마스크는0 .)

다음은 64 비트 빼기 및 xor입니다.

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61,
  54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62,
  46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
  25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v ^ (v-1)) * 0x03F79D71B4CB0A89U)) >> 58];

참고로 64 비트 버전의 negate 및 mask 메서드는 다음과 같습니다.

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
  62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
  63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
  46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x03F79D71B4CB0A89U)) >> 58];

(v ^ (v-1))작품이 제공됩니다 v != 0. 의 경우 v == 0동안이 0xFF로 .... FF를 리턴 (v & -v)제로 제공 (그런데 잘못도 버피는 적어도 그것은 합리적인 결과로 연결).
CiaPan 2014-06-12

@CiaPan : 좋은 지적입니다. 63 번째 인덱스에 0을 넣어이 문제를 해결할 다른 De Bruijn 숫자가 있다고 생각합니다.
jnm2 2014-06-12

Duh, 그게 문제가 아닙니다. 0과 0x8000000000000000은 둘 다 0xFFFFFFFFFFFFFFFF v ^ (v-1)가되므로 구분할 수 없습니다. 내 시나리오에서는 0이 입력되지 않습니다.
jnm2 2014-06-12

1

하위 비트가 설정되어 있는지 확인할 수 있습니다. 그렇다면 나머지 비트의 하위 순서를 살펴보십시오. 예 :

32bit int-처음 16 개가 설정되어 있는지 확인합니다. 그렇다면 처음 8 개 중 하나가 설정되어 있는지 확인하십시오. 그렇다면, ....

그렇지 않은 경우 상위 16 개가 설정되어 있는지 확인합니다.

본질적으로 이진 검색입니다.


1

내 대답을 참조하십시오 여기 찾기 위해 것을 제외하고, 하나의 x86 명령어로 작업을 수행하는 방법에 대한 최소한 당신이 원하는 것 비트 중요한 세트 BSF( "스캔 앞으로 비트") 명령을 대신 BSR이 기술을.


1

가장 빠르지는 않지만 아주 좋은 또 다른 솔루션입니다.
적어도 가지가 없습니다. ;)

uint32 x = ...;  // 0x00000001  0x0405a0c0  0x00602000
x |= x <<  1;    // 0x00000003  0x0c0fe1c0  0x00e06000
x |= x <<  2;    // 0x0000000f  0x3c3fe7c0  0x03e1e000
x |= x <<  4;    // 0x000000ff  0xffffffc0  0x3fffe000
x |= x <<  8;    // 0x0000ffff  0xffffffc0  0xffffe000
x |= x << 16;    // 0xffffffff  0xffffffc0  0xffffe000

// now x is filled with '1' from the least significant '1' to bit 31

x = ~x;          // 0x00000000  0x0000003f  0x00001fff

// now we have 1's below the original least significant 1
// let's count them

x = x & 0x55555555 + (x >>  1) & 0x55555555;
                 // 0x00000000  0x0000002a  0x00001aaa

x = x & 0x33333333 + (x >>  2) & 0x33333333;
                 // 0x00000000  0x00000024  0x00001444

x = x & 0x0f0f0f0f + (x >>  4) & 0x0f0f0f0f;
                 // 0x00000000  0x00000006  0x00000508

x = x & 0x00ff00ff + (x >>  8) & 0x00ff00ff;
                 // 0x00000000  0x00000006  0x0000000d

x = x & 0x0000ffff + (x >> 16) & 0x0000ffff;
                 // 0x00000000  0x00000006  0x0000000d
// least sign.bit pos. was:  0           6          13

모두 얻을 수 1LSB, 사용에 최하위 1의를 ((x & -x) - 1) << 1대신
phuclv

더 빠른 방법 :x ^ (x-1)
phuclv

1
unsigned GetLowestBitPos(unsigned value)
{
    if (value & 1) return 1;
    if (value & 2) return 2;
    if (value & 4) return 3;
    if (value & 8) return 4;
    if (value & 16) return 5;
    if (value & 32) return 6;
    if (value & 64) return 7;
    if (value & 128) return 8;
    if (value & 256) return 9;
    if (value & 512) return 10;
    if (value & 1024) return 11;
    if (value & 2048) return 12;
    if (value & 4096) return 13;
    if (value & 8192) return 14;
    if (value & 16384) return 15;
    if (value & 32768) return 16;
    if (value & 65536) return 17;
    if (value & 131072) return 18;
    if (value & 262144) return 19;
    if (value & 524288) return 20;
    if (value & 1048576) return 21;
    if (value & 2097152) return 22;
    if (value & 4194304) return 23;
    if (value & 8388608) return 24;
    if (value & 16777216) return 25;
    if (value & 33554432) return 26;
    if (value & 67108864) return 27;
    if (value & 134217728) return 28;
    if (value & 268435456) return 29;
    if (value & 536870912) return 30;
    return 31;
}

모든 숫자의 50 %는 코드의 첫 번째 줄에 반환됩니다.

모든 숫자의 75 %는 코드의 처음 두 줄에 반환됩니다.

모든 숫자의 87 %가 코드의 처음 3 줄에 반환됩니다.

모든 숫자의 94 %가 코드의 처음 4 줄에 반환됩니다.

모든 숫자의 97 %가 코드의 처음 5 줄에 반환됩니다.

기타

이 코드에 대한 최악의 시나리오가 얼마나 비효율적이라고 불평하는 사람들은 그 조건이 얼마나 드물게 발생하는지 이해하지 못한다고 생각합니다.


3
그리고 최악의 경우 32 개의 분기 예측 오류 :)

1
이건 최소한 스위치로 만들 수는 없나요 ...?
Steven Lu

"적어도 스위치로 만들 수는 없나요 ...?" 가능하다는 것을 암시하기 전에 그렇게하려고 했습니까? 언제부터 스위치의 경우에 바로 계산을 할 수 있습니까? 클래스가 아니라 조회 테이블입니다.
j riv

1

"The art of programming, part 4"에서 '매직 마스크'를 사용하여이 영리한 트릭을 발견했습니다. 이는 n 비트 숫자에 대해 O (log (n)) 시간에 수행합니다. [log (n) 추가 공간 포함]. 세트 비트를 확인하는 일반적인 솔루션은 O (n)이거나 조회 테이블을 위해 O (n) 추가 공간이 필요하므로 이는 좋은 절충안입니다.

매직 마스크 :

m0 = (...............01010101)  
m1 = (...............00110011)
m2 = (...............00001111)  
m3 = (.......0000000011111111)
....

핵심 아이디어 : x = 1 * [(x & m0) = 0] + 2 * [(x & m1) = 0] + 4 * [(x & m2) = 0] + ...

int lastSetBitPos(const uint64_t x) {
    if (x == 0)  return -1;

    //For 64 bit number, log2(64)-1, ie; 5 masks needed
    int steps = log2(sizeof(x) * 8); assert(steps == 6);
    //magic masks
    uint64_t m[] = { 0x5555555555555555, //     .... 010101
                     0x3333333333333333, //     .....110011
                     0x0f0f0f0f0f0f0f0f, //     ...00001111
                     0x00ff00ff00ff00ff, //0000000011111111 
                     0x0000ffff0000ffff, 
                     0x00000000ffffffff };

    //Firstly extract only the last set bit
    uint64_t y = x & -x;

    int trailZeros = 0, i = 0 , factor = 0;
    while (i < steps) {
        factor = ((y & m[i]) == 0 ) ? 1 : 0;
        trailZeros += factor * pow(2,i);
        ++i;
    }
    return (trailZeros+1);
}

1

C ++ 11을 사용할 수있는 경우 컴파일러가 때때로 작업을 수행 할 수 있습니다. :)

constexpr std::uint64_t lssb(const std::uint64_t value)
{
    return !value ? 0 : (value % 2 ? 1 : lssb(value >> 1) + 1);
}

결과는 1 기반 인덱스입니다.


1
영리하지만 입력이 컴파일 타임 상수가 아닌 경우 치명적인 불량 어셈블리로 컴파일됩니다. godbolt.org/g/7ajMyT . (gcc를 사용한 비트에 대한 멍청한 루프 또는 clang을 사용한 실제 재귀 함수 호출) gcc / clang은 ffs()컴파일 타임에 평가할 수 있으므로 상수 전파가 작동하기 위해 이것을 사용할 필요가 없습니다. (물론 inline-asm을 피해야합니다.) 정말로 C ++ 11로 작동하는 것이 필요하다면 constexprGNU C를 사용할 수 있습니다 __builtin_ffs.
Peter Cordes

0

이것은 @Anton Tykhyy 답변에 관한 것입니다.

다음은 캐스트를 없애고 64 비트 결과를 32 비트로 잘라서 VC ++ 17에서 경고를 제거하는 C ++ 11 constexpr 구현입니다.

constexpr uint32_t DeBruijnSequence[32] =
{
    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
constexpr uint32_t ffs ( uint32_t value )
{
    return  DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

0x1 및 0x0 모두 0을 반환하는 문제를 해결하려면 다음을 수행 할 수 있습니다.

constexpr uint32_t ffs ( uint32_t value )
{
    return (!value) ? 32 : DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

그러나 컴파일러가 호출을 전처리 할 수 ​​없거나 처리하지 않을 경우 계산에 몇 개의주기가 추가됩니다.

마지막으로, 관심이있는 경우 코드가 의도 한 작업을 수행하는지 확인하는 정적 어설 션 목록이 있습니다.

static_assert (ffs(0x1) == 0, "Find First Bit Set Failure.");
static_assert (ffs(0x2) == 1, "Find First Bit Set Failure.");
static_assert (ffs(0x4) == 2, "Find First Bit Set Failure.");
static_assert (ffs(0x8) == 3, "Find First Bit Set Failure.");
static_assert (ffs(0x10) == 4, "Find First Bit Set Failure.");
static_assert (ffs(0x20) == 5, "Find First Bit Set Failure.");
static_assert (ffs(0x40) == 6, "Find First Bit Set Failure.");
static_assert (ffs(0x80) == 7, "Find First Bit Set Failure.");
static_assert (ffs(0x100) == 8, "Find First Bit Set Failure.");
static_assert (ffs(0x200) == 9, "Find First Bit Set Failure.");
static_assert (ffs(0x400) == 10, "Find First Bit Set Failure.");
static_assert (ffs(0x800) == 11, "Find First Bit Set Failure.");
static_assert (ffs(0x1000) == 12, "Find First Bit Set Failure.");
static_assert (ffs(0x2000) == 13, "Find First Bit Set Failure.");
static_assert (ffs(0x4000) == 14, "Find First Bit Set Failure.");
static_assert (ffs(0x8000) == 15, "Find First Bit Set Failure.");
static_assert (ffs(0x10000) == 16, "Find First Bit Set Failure.");
static_assert (ffs(0x20000) == 17, "Find First Bit Set Failure.");
static_assert (ffs(0x40000) == 18, "Find First Bit Set Failure.");
static_assert (ffs(0x80000) == 19, "Find First Bit Set Failure.");
static_assert (ffs(0x100000) == 20, "Find First Bit Set Failure.");
static_assert (ffs(0x200000) == 21, "Find First Bit Set Failure.");
static_assert (ffs(0x400000) == 22, "Find First Bit Set Failure.");
static_assert (ffs(0x800000) == 23, "Find First Bit Set Failure.");
static_assert (ffs(0x1000000) == 24, "Find First Bit Set Failure.");
static_assert (ffs(0x2000000) == 25, "Find First Bit Set Failure.");
static_assert (ffs(0x4000000) == 26, "Find First Bit Set Failure.");
static_assert (ffs(0x8000000) == 27, "Find First Bit Set Failure.");
static_assert (ffs(0x10000000) == 28, "Find First Bit Set Failure.");
static_assert (ffs(0x20000000) == 29, "Find First Bit Set Failure.");
static_assert (ffs(0x40000000) == 30, "Find First Bit Set Failure.");
static_assert (ffs(0x80000000) == 31, "Find First Bit Set Failure.");

0

로그를 찾는 데 약간의 비용이 들지만 간단한 대안이 있습니다.

if(n == 0)
  return 0;
return log2(n & -n)+1;   //Assuming the bit index starts from 1

-3

최근 싱가포르 프리미어가 그가 페이스 북에 쓴 프로그램을 게시 한 것을 보았습니다.

논리는 단순히 "value & -value"입니다. 0x0FF0, 0FF0 & (F00F + 1), 즉 0x0010이 있다고 가정하면 가장 낮은 1이 4 번째 비트에 있음을 의미합니다. :)


1
이것은 가장 낮은 비트를 분리하지만이 질문이 요구하는 위치를 제공하지 않습니다.
rhashimoto

나는 이것이 마지막 비트를 찾는데도 효과가 있다고 생각하지 않습니다.
yyny 2011

value & ~ value는 0입니다.
khw

죄송합니다. 눈이 나빠집니다. 마이너스를 물결표로 착각했습니다. 내 의견을 무시
KHW

-8

리소스가 있다면 속도를 높이기 위해 메모리를 희생 할 수 있습니다.

static const unsigned bitPositions[MAX_INT] = { 0, 0, 1, 0, 2, /* ... */ };

unsigned GetLowestBitPos(unsigned value)
{
    assert(value != 0); // handled separately
    return bitPositions[value];
}

참고 : 이 테이블은 최소 4GB (반환 유형을으로두면 16GB unsigned)를 사용합니다. 이것은 하나의 제한된 자원 (RAM)을 다른 자원 (실행 속도)으로 거래하는 예입니다.

함수를 이식 가능한 상태로 유지하고 어떤 비용 으로든 가능한 한 빨리 실행해야하는 경우이 방법이 적합합니다. 대부분의 실제 애플리케이션에서 4GB 테이블은 비현실적입니다.


1
입력 범위는 이미 매개 변수 유형에 의해 지정되어 있습니다. 'unsigned'는 32 비트 값이므로 아니요, 괜찮지 않습니다.
Brian

3
음 ... 신화적인 시스템과 OS에 페이징 메모리 개념이 있습니까? 비용이 얼마나 들까 요?
Mikeage

14
이것은 대답이 아닙니다. 귀하의 솔루션은 모든 실제 애플리케이션에서 완전히 비현실적이며이를 "상쇄"라고 부르는 것은 분명하지 않습니다. 단일 기능에 전념 할 16GB의 램이있는 신화 시스템은 존재하지 않습니다. 당신은 "양자 컴퓨터 사용"이라고 대답했을 것입니다.
Brian

3
속도를 위해 메모리를 희생 하시겠습니까? 4GB 이상의 조회 테이블은 현재 존재하는 컴퓨터의 캐시에 절대 맞지 않으므로 여기에있는 거의 모든 답변보다 느릴 것이라고 생각합니다.

1
아아. 이 끔찍한 대답은 계속 나를 괴롭 :)힙니다. @Dan : 메모리 캐싱에 대해 맞습니다. 위의 Mikeage의 의견을 참조하십시오.
e. James
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.