32 비트 정수로 설정된 비트 수를 계산하는 방법은 무엇입니까?


868

숫자 7을 나타내는 8 비트는 다음과 같습니다.

00000111

3 비트가 설정됩니다.

32 비트 정수에서 설정 비트 수를 결정하는 알고리즘은 무엇입니까?


101
이것이 해밍 웨이트 BTW입니다.
Purfideas

11
이것에 대한 실제 응용 프로그램은 무엇입니까? (이것은 비판으로 간주되지 않습니다-그냥 궁금합니다.)
jonmorgan

8
통신에서 간단한 오류 감지로 사용 된 패리티 비트 (찾아보기) 계산
Dialecticus

8
@Dialecticus, 패리티 비트를 계산하는 것이 해밍 가중치를 계산하는 보다 저렴 합니다
finnw

15
@spookyjon 인접 행렬로 표현되는 그래프가 있다고 가정 해 봅시다. 기본적으로 비트 세트입니다. 정점의 가장자리 수를 계산하려면 비트 세트에서 한 행의 해밍 가중치를 계산합니다.
fuz

답변:


850

이를 ' 해밍 가중치 ', '인수'또는 '옆으로 추가'라고합니다.

'최상의'알고리즘은 실제로 사용중인 CPU와 사용 패턴에 따라 다릅니다.

일부 CPU에는 단일 내장 명령이 있으며 다른 CPU에는 비트 벡터에 작용하는 병렬 명령이 있습니다. 병렬 명령어 ( popcnt지원되는 CPU 의 x86와 같은 )는 거의 가장 빠를 것입니다. 일부 다른 아키텍처에는 사이클 당 비트를 테스트하는 마이크로 코드 루프로 구현 된 느린 명령이있을 수 있습니다 ( 인용 필요 ).

CPU에 큰 캐시가 있거나 타이트한 루프에서 이러한 명령을 많이 수행하는 경우 미리 채워진 테이블 조회 방법이 매우 빠릅니다. 그러나 CPU가 메인 메모리에서 일부 테이블을 가져와야하는 '캐시 미스 (cache miss)'의 비용으로 인해 문제가 발생할 수 있습니다. (테이블을 작게 유지하려면 각 바이트를 별도로 찾으십시오.)

바이트가 대부분 0 또는 대부분 1임을 알면 이러한 시나리오에 매우 효율적인 알고리즘이 있습니다.

나는 매우 좋은 범용 알고리즘이 '병렬'또는 '가변 정밀도 SWAR 알고리즘'으로 알려진 다음과 같다고 생각합니다. 나는 이것을 C와 유사한 의사 언어로 표현했다. 특정 언어 (예를 들어 C ++의 경우 uint32_t를 사용하고 Java의 >>>를 사용)로 작동하도록 조정해야 할 수도 있습니다.

int numberOfSetBits(uint32_t i)
{
     // Java: use int, and use >>> instead of >>
     // C or C++: use uint32_t
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

JavaScript의 경우 : 성능 을 위해 정수강제|0 변환하십시오. 첫 번째 행을i = (i|0) - ((i >> 1) & 0x55555555);

이것은 논의 된 알고리즘 중 최악의 최악의 동작을 가지므로 사용자가 사용하는 사용 패턴이나 값을 효율적으로 처리합니다.


이 SWAR 비트 핵 작동 방식 :

i = i - ((i >> 1) & 0x55555555);

첫 번째 단계는 홀수 / 짝수 비트를 분리하고 정렬하여 추가하고 추가하기 위해 최적화 된 마스킹 버전입니다. 이렇게하면 2 비트 누산기 ( SWAR = SIMD 내의 레지스터 ) 에서 16 개의 개별 추가가 효과적으로 수행 됩니다. 처럼 (i & 0x55555555) + ((i>>1) & 0x55555555).

다음 단계는 16x 2 비트 누산기의 홀수 / 짝수 8을 취하고 다시 더하여 8x 4 비트 합을 생성합니다. 이번에는 i - ...최적화가 불가능하므로 시프트 전 / 후 마스크 만 수행합니다. 레지스터에서 32 비트 상수를 별도로 구성해야하는 ISA를 컴파일 할 때 시프트하기 전에 0x33...대신 동일한 상수를 두 번 사용 0xccc...하는 것이 좋습니다.

마지막 이동 및 추가 단계는 (i + (i >> 4)) & 0x0F0F0F0F4 배속 8 ​​비트 누산기 로 확대됩니다. 해당 입력 비트의 모든 4 비트가 설정된 경우 4 비트 누산기의 최대 값이이므로 이전 대신 추가 한 후 마스킹 4됩니다. 4 + 4 = 8은 여전히 ​​4 비트에 맞으므로 니블 요소 사이의 캐리는 불가능 i + (i >> 4)합니다.

지금까지 이것은 몇 가지 영리한 최적화로 SWAR 기술을 사용하는 꽤 일반적인 SIMD입니다. 2 단계 이상의 동일한 패턴으로 계속하면 2x 16 비트, 1x 32 비트 카운트로 확장 될 수 있습니다. 그러나 빠른 하드웨어 곱셈을 가진 머신에는 더 효율적인 방법이 있습니다.

"요소"가 충분하지 않으면 마법 상수를 곱하면 모든 요소를 ​​최상위 요소에 합할 수 있습니다. 이 경우 바이트 요소입니다. 곱하기는 왼쪽 이동과 더하기에 의해 수행되므로 결과 는에 곱해 x * 0x01010101집니다 x + (x<<8) + (x<<16) + (x<<24). 우리의 8 비트 요소는이 너비 가 상위 8 비트에 들어 가지 않을만큼 충분히 넓습니다 (그리고 적은 수를 보유) .

이 64 비트 버전은 0x0101010101010101 승수를 사용하여 64 비트 정수에서 8x 8 비트 요소를 수행하고로 높은 바이트를 추출 할 수 >>56있습니다. 따라서 추가 단계를 거치지 않고 더 넓은 상수를 사용합니다. __builtin_popcountll하드웨어 popcnt명령어를 사용할 수없는 경우 x86 시스템에서 GCC가 사용합니다 . 이를 위해 내장 또는 내장 함수를 사용할 수있는 경우 컴파일러에서 대상별 최적화를 수행 할 수있는 기회를 제공하십시오.


더 넓은 벡터를위한 완전한 SIMD (예 : 전체 배열 계산)

이 비트 -SWAR 알고리즘은 SIMD는 있지만 사용 가능한 팝 카운트 명령이없는 CPU의 속도를 높이기 위해 단일 정수 레지스터 대신 여러 벡터 요소에서 한 번에 병렬 처리되도록 병렬 처리 할 수 ​​있습니다. (예 : Nehalem 이상이 아닌 모든 CPU에서 실행되어야하는 x86-64 코드)

그러나 popcount에 벡터 명령어를 사용하는 가장 좋은 방법은 일반적으로 variable-shuffle을 사용하여 각 바이트의 시간에 4 비트 씩 테이블 조회를 병렬로 수행하는 것입니다. (4 비트는 벡터 레지스터에 보유 된 16 개의 엔트리 테이블을 인덱스합니다).

Intel CPU에서 하드웨어 64 비트 popcnt 명령은 SSSE3 PSHUFB비트 병렬 구현 을 2 배 정도 수행 할 수 있지만 컴파일러에서 올바르게 얻는 경우 에만 가능합니다 . 그렇지 않으면 SSE가 크게 앞설 수 있습니다. 최신 컴파일러 버전은 인텔popcnt 잘못된 종속성 문제를 알고 있습니다.

참고 문헌 :


87
하아! NumberOfSetBits () 함수를 좋아하지만 코드 검토를 통해 행운을 빕니다. :-)
Jason S

37
unsigned int부호 비트 합병증이 없음을 쉽게 표시하기 위해을 사용해야 할 수도 있습니다. 또한 uint32_t모든 플랫폼에서 기대하는 것을 얻는 것처럼 더 안전합니까?
Craig McQueen

35
@nonnb : 실제로 작성된 코드는 버그가 많으며 유지 관리가 필요합니다. >>음수 값에 대한 구현 정의입니다. 인수는로 변경 (또는 캐스트)해야 unsigned하며 코드는 32 비트에 따라 다르므로 아마도을 사용해야합니다 uint32_t.
R .. GitHub 중지 지원 얼음

6
정말 마술이 아닙니다. 비트 세트를 추가하지만 영리한 최적화로 그렇게합니다. 답변에 제공된 wikipedia 링크는 진행 상황을 설명하는 데 도움이되지만 한 줄씩 진행하겠습니다. 1) 모든 비트 쌍의 비트 수를 세어 해당 비트 쌍에 해당 수를 넣습니다 (00, 01 또는 10이 있음). 여기서 "영리한"비트는 하나의 마스크를 피하는 빼기입니다. 2) 비트 쌍의 합 쌍을 해당 니블에 추가하십시오. 여기서 영리한 것은 아니지만 각 니블의 값은 이제 0-4입니다. (계속)
dash-tom-bang

8
또 다른 참고 사항은 단순히 상수를 적절하게 확장하여 64 및 128 비트 레지스터로 확장하는 것입니다. 흥미롭게도 (나에게), 이러한 상수는 ~ 0 / 3, 5, 17 및 255입니다. 전자는 2 ^ n + 1입니다. 이 모든 것이 당신이 그것을 응시하고 샤워에서 그것에 대해 생각할수록 더 의미가 있습니다. :)
dash-tom-bang

214

또한 컴파일러의 내장 함수를 고려하십시오.

예를 들어 GNU 컴파일러에서는 다음을 사용할 수 있습니다.

int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);

최악의 경우 컴파일러는 함수에 대한 호출을 생성합니다. 가장 좋은 경우 컴파일러는 동일한 작업을 더 빨리 수행하기 위해 CPU 명령을 내 보냅니다.

GCC 내장 함수는 여러 플랫폼에서 작동합니다. Popcount는 x86 아키텍처에서 주류가 될 것이므로 이제 내장 함수를 사용하는 것이 좋습니다. 다른 아키텍처에는 몇 년 동안 인구수가 있습니다.


x86에서는 컴파일러에게 동일한 세대에 추가 된 벡터 명령어를 사용 하거나 popcnt명령어를 지원한다고 가정 할 수 있습니다 . GCC x86 옵션을 참조하십시오 . (또는 코드에서 가정하고 조정하려는 CPU)가 좋은 선택이 될 수 있습니다. 이전 CPU에서 결과 바이너리를 실행하면 잘못된 명령 오류가 발생합니다.-mpopcnt-msse4.2-march=nehalem-march=

바이너리를 빌드 한 머신에 최적화 된 바이너리를 만들려면 -march=native gcc, clang 또는 ICC와 함께 사용하십시오 .

MSVC는 x86 popcnt명령어에 대한 내장 함수를 제공 하지만 gcc와 달리 실제로는 하드웨어 명령어에 고유하며 하드웨어 지원이 필요합니다.


std::bitset<>::count()내장 대신 사용

이론적으로 대상 CPU에 대해 효율적으로 popcount하는 방법을 알고있는 모든 컴파일러는 ISO C ++를 통해 해당 기능을 노출해야합니다 std::bitset<>. 실제로 일부 대상 CPU의 경우 비트 핵 AND / shift / ADD를 사용하는 것이 좋습니다.

하드웨어 popcount가 선택적인 확장 (x86과 같은) 인 대상 아키텍처의 경우 모든 컴파일러가 std::bitset사용 가능한 경우이를 활용 하는 것은 아닙니다 . 예를 들어, MSVC는 popcnt컴파일 타임에 지원 을 활성화 할 수있는 방법이 없으며 기술적으로 별도의 기능 비트가 있지만 SSE4.2를 의미하는 경우에도 항상 테이블 조회를 사용 합니다 ./Ox /arch:AVXpopcnt

그러나 적어도 어느 곳에서나 휴대 가능한 것을 얻을 수 있으며 올바른 대상 옵션이있는 gcc / clang을 사용하면이를 지원하는 아키텍처에 대한 하드웨어 팝 카운트를 얻을 수 있습니다.

#include <bitset>
#include <limits>
#include <type_traits>

template<typename T>
//static inline  // static if you want to compile with -mpopcnt in one compilation unit but not others
typename std::enable_if<std::is_integral<T>::value,  unsigned >::type 
popcount(T x)
{
    static_assert(std::numeric_limits<T>::radix == 2, "non-binary type");

    // sizeof(x)*CHAR_BIT
    constexpr int bitwidth = std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    // std::bitset constructor was only unsigned long before C++11.  Beware if porting to C++03
    static_assert(bitwidth <= std::numeric_limits<unsigned long long>::digits, "arg too wide for std::bitset() constructor");

    typedef typename std::make_unsigned<T>::type UT;        // probably not needed, bitset width chops after sign-extension

    std::bitset<bitwidth> bs( static_cast<UT>(x) );
    return bs.count();
}

Godbolt 컴파일러 탐색기 에서 gcc, clang, icc 및 MSVC의 asm을 참조하십시오 .

x86-64는 다음을 gcc -O3 -std=gnu++11 -mpopcnt방출합니다.

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

PowerPC64는 다음을 gcc -O3 -std=gnu++11방출합니다 ( intarg 버전의 경우).

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

이 소스는 x86 또는 GNU에 국한되지 않지만 gcc / clang / icc를 사용하여 x86에 대해서만 잘 컴파일됩니다.

또한 단일 명령 popcount가없는 아키텍처에 대한 gcc의 폴백은 한 번에 바이트 당 테이블 조회입니다. 예를 들어 이것은 ARM 에게는 좋지 않습니다 .


5
나는 이것이 일반적으로 좋은 습관이라는 것에 동의하지만, XCode / OSX / Intel에서는 여기에 게시 된 대부분의 제안보다 느린 코드를 생성하는 것으로 나타났습니다. 자세한 내용은 내 답변을 참조하십시오.

5
인텔 i5 / i7에는 범용 레지스터를 사용하여이를 수행하는 SSE4 명령어 POPCNT가 있습니다. 내 시스템의 GCC는이 내장 함수를 사용하여 해당 명령을 내 보내지 않습니다. 아직 -march = nehalem 옵션이 없기 때문에 추측합니다.
matja

3
@matja, 내 GCC 4.4.1 방출하는 popcnt 명령은 내가 -msse4.2로 컴파일하는 경우
닐스 Pipenbrinck에게

74
c ++를 사용하십시오 std::bitset::count. 인라인 후 단일 __builtin_popcount호출로 컴파일됩니다 .
deft_code

1
@nlucaroni 글쎄요. 시간이 바뀌고 있습니다. 나는 2008 년에이 답변을 썼다. 오늘날 우리는 네이티브 popcount를 가지고 있고 플랫폼이 허용한다면 내장 함수는 단일 어셈블러 명령문으로 컴파일 될 것이다.
Nils Pipenbrinck

184

제 생각에는 "최고의"솔루션은 다른 프로그래머 (또는 2 년 후 원래 프로그래머)가 많은 의견없이 읽을 수있는 솔루션입니다. 일부는 이미 제공 한 가장 빠르고 영리한 솔루션을 원할 수 있지만 언제든지 영리함보다 가독성을 선호합니다.

unsigned int bitCount (unsigned int value) {
    unsigned int count = 0;
    while (value > 0) {           // until all bits are zero
        if ((value & 1) == 1)     // check lower bit
            count++;
        value >>= 1;              // shift bits, removing lower bit
    }
    return count;
}

더 빠른 속도를 원하고 (후임자를 돕기 위해 잘 문서화한다고 가정하면) 테이블 조회를 사용할 수 있습니다.

// Lookup table for fast calculation of bits set in 8-bit unsigned char.

static unsigned char oneBitsInUChar[] = {
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F (<- n)
//  =====================================================
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, // 0n
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, // 1n
    : : :
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, // Fn
};

// Function for fast calculation of bits set in 16-bit unsigned short.

unsigned char oneBitsInUShort (unsigned short x) {
    return oneBitsInUChar [x >>    8]
         + oneBitsInUChar [x &  0xff];
}

// Function for fast calculation of bits set in 32-bit unsigned int.

unsigned char oneBitsInUInt (unsigned int x) {
    return oneBitsInUShort (x >>     16)
         + oneBitsInUShort (x &  0xffff);
}

이들은 특정 데이터 유형 크기에 의존하기 때문에 이식성이 떨어집니다. 그러나 많은 성능 최적화가 어쨌든 이식 가능하지 않기 때문에 문제가되지 않을 수 있습니다. 이식성을 원한다면 읽기 쉬운 솔루션을 고수하겠습니다.


21
2로 나누고 "shift bits ..."로 주석을 달지 말고 shift 연산자 (>>)를 사용하고 주석을 남겨 두십시오.
indiv

9
그것을 대체하는 더 이해되지 아니 if ((value & 1) == 1) { count++; }로를 count += value & 1?
Ponkadoodle

21
아니요,이 경우 가장 좋은 솔루션은 가장 읽기 쉬운 솔루션이 아닙니다. 여기서 가장 좋은 알고리즘이 가장 빠릅니다.
NikiC

21
당신이 저를 자유롭게 투표 할 수는 있지만, 전적으로 당신의 의견입니다. "최고"를 정량화하는 방법에 대한 질문에는 언급되지 않았으며, "성능"또는 "빠른"이라는 단어는 어디에도 보이지 않습니다. 그래서 내가 읽을 수있는 것을 선택했습니다.
paxdiablo

3
나는 3 년 후에이 답변을 읽고 있으며, 읽을 수 있고 더 많은 의견을 가지고 있기 때문에 가장 좋은 답변이라고 생각합니다. 기간.
waka-waka-waka

98

Hacker 's Delight, p. 66, 그림 5-2

int pop(unsigned x)
{
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

~ 20-ish 명령어로 실행 (아치에 따라 다름), 분기 없음.

해커의 기쁨 유쾌합니다! 추천.


8
Java 메소드 Integer.bitCount(int)는 이와 동일한 정확한 구현을 사용합니다.
Marco Bolis

이것을 따르는 데 약간의 어려움이 있습니다 .32 비트 대신 16 비트 값만 고려하면 어떻게 변경됩니까?
Jeremy Blum 2012

해커들이 기뻐할 수도 있지만, 이것을 pop대신해 population_count(또는 pop_cnt약어가 필요한 경우) 누군가에게 좋은 발걸음을 줄 것입니다. @MarcoBolis Java의 모든 버전에 해당되는 것으로 가정하지만 공식적으로 구현에 따라 다릅니다.)
Maarten Bodewes

그리고 이것은 수락 된 답변의 코드와 같이 곱셈을 필요로하지 않습니다.
Alex

64 비트로 일반화 할 때 문제가 있음에 유의하십시오. 마스크로 인해 결과는 64 일 수 없습니다.
Albert van der Horst

76

조회 테이블과 popcount 를 사용하지 않고 가장 빠른 방법 은 다음과 같습니다. 12 개의 연산으로 설정된 비트 수를 계산합니다.

int popcount(int v) {
    v = v - ((v >> 1) & 0x55555555);                // put count of each 2 bits into those 2 bits
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // put count of each 4 bits into those 4 bits  
    return c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}

두 세트로 나누어 총 세트 비트 수를 세고, 세트 비트 수를 반으로 세고 합산하면 효과가 있습니다. Divide and Conquer패러다임 이라고도합니다 . 자세히 알아 보자 ..

v = v - ((v >> 1) & 0x55555555); 

두 비트의 비트 수 0b000b01또는 0b10입니다. 이것을 2 비트로 해결해 봅시다.

 ---------------------------------------------
 |   v    |   (v >> 1) & 0b0101   |  v - x   |
 ---------------------------------------------
   0b00           0b00               0b00   
   0b01           0b00               0b01     
   0b10           0b01               0b01
   0b11           0b01               0b10

마지막 열은 두 비트 쌍마다 설정된 비트 수를 나타냅니다. 두 개의 비트 번호 인 경우 >= 2 (0b10)다음 and생산하고 0b01, 다른 사람이 생산 0b00.

v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 

이 문장은 이해하기 쉬워야합니다. 첫 번째 연산 후에 우리는 매 2 비트마다 세트 비트 수를 갖습니다. 이제 우리는 매 4 비트마다 그 수를 요약합니다.

v & 0b00110011         //masks out even two bits
(v >> 2) & 0b00110011  // masks out odd two bits

그런 다음 위의 결과를 요약하여 총 4 비트 세트 비트 수를 제공합니다. 마지막 진술이 가장 까다 롭습니다.

c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

더 자세히 살펴 보자 ...

v + (v >> 4)

두 번째 진술과 비슷합니다. 대신 세트 그룹을 4 그룹으로 세고 있습니다. 우리는 이전 작업으로 인해 모든 니블에 설정된 비트 수가 있음을 알고 있습니다. 예를 봅시다. 바이트가 있다고 가정하자 0b01000010. 첫 번째 니블에 4 비트가 설정되어 있고 두 번째 니블에 2 비트가 설정되어 있음을 의미합니다. 이제 우리는 그 니블을 함께 추가합니다.

0b01000010 + 0b01000000

첫 번째 니블에서 바이트 단위의 세트 비트 수를 제공 0b01100010하므로 숫자에있는 모든 바이트의 마지막 4 바이트를 마스킹합니다 (삭제).

0b01100010 & 0xF0 = 0b01100000

이제 모든 바이트에는 설정된 비트 수가 있습니다. 우리는 그것들을 모두 합쳐야합니다. 트릭은 0b10101010흥미로운 속성을 가진 결과를 곱하는 것 입니다. 숫자에 4 바이트가 있으면 A B C D이 바이트로 새로운 숫자가됩니다 A+B+C+D B+C+D C+D D. 4 바이트 숫자는 최대 32 비트 세트를 가질 수 있으며 이는로 표시 될 수 있습니다 0b00100000.

우리가 지금 필요한 것은 모든 바이트의 모든 세트 비트의 합을 가진 첫 번째 바이트입니다 >> 24. 이 알고리즘은 32 bit단어 용으로 설계 되었지만 단어 용 으로 쉽게 수정할 수 있습니다 64 bit.


c = 대해 무엇입니까 ? 제거 해야하는 것 같습니다. 또한 몇 가지 고전적인 경고를 피하기 위해 여분의 paren set A "((((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24"를 제안하십시오.
chux-복원 Monica Monica

4
중요한 기능은이 32 비트 루틴이 popcount(int v)및 모두에 대해 작동한다는 것 popcount(unsigned v)입니다. 이식성을 위해, popcount(uint32_t v)* 0x1010101 부분과 같은 것을 고려 하십시오.
chux-복원 모니카

소스 ? (책, 링크, invetors의 이름 등)은 매우 환영받을 것입니다. 그런 다음 코드베이스에 소스 코드를 붙여 넣을 수 있습니다.
v.oddou

1
마지막 줄을 더 명확하게 작성해야한다고 생각합니다. return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;따라서 실제로 수행중인 작업을 확인하기 위해 문자를 계산할 필요가 없습니다 (첫 번째를 버린 이후 0실수로 잘못된 비트 패턴을 마스크로 사용했다고 생각합니다) -7 글자 만 있고 8 글자는 없다는 것을 알기 전까지는 말입니다.
emem

즉, 곱셈 0x01010101에 의해 프로세서에 따라 느려질 수 있습니다. 예를 들어, 나의 오래된 PowerBook G4에서 1 곱셈은 4 더하기만큼 느 렸습니다 (나눗셈만큼 나쁘지 않습니다. 1 나누기는 23 더하기만큼 느 렸습니다).
George Koehler

54

지루 해졌고 세 가지 접근 방식을 10 억 회 반복했습니다. 컴파일러는 gcc -O3입니다. CPU는 1 세대 Macbook Pro에 넣은 모든 것입니다.

3.7 초에서 다음이 가장 빠릅니다.

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

두 번째 장소는 동일한 코드로 이동하지만 2 하프 워드 대신 4 바이트를 찾습니다. 약 5.5 초가 걸렸습니다.

3 위는 약간 꼬인 '옆으로 추가'접근 방식으로 8.6 초가 걸렸습니다.

4 위는 부끄러운 11 초에 GCC의 __builtin_popcount ()로갑니다.

한 번에 한 비트 씩 계산하는 것이 느리게 진행되었으며, 완료 될 때까지 기다리는 것이 지루했습니다.

따라서 무엇보다도 성능에 관심이 있다면 첫 번째 방법을 사용하십시오. 걱정하지만 64Kb의 RAM을 소비하기에 충분하지 않은 경우 두 번째 방법을 사용하십시오. 그렇지 않으면 한 번에 한 비트 씩 읽기 가능하지만 느리게 접근하십시오.

비트 트위들 링 접근법을 사용하려는 상황을 생각하기는 어렵습니다.

편집 : 비슷한 결과는 여기 .


49
@Mike, 테이블이 캐시에 있으면 테이블 기반 접근 방식이 적합하지 않습니다. 이는 마이크로 벤치 마크에서 발생합니다 (예 : 긴밀한 루프에서 수백만 번의 테스트 수행). 그러나 캐시 미스에는 약 200 사이클이 걸리며 가장 순진한 팝 카운트조차도 더 빠릅니다. 항상 응용 프로그램에 따라 다릅니다.
Nils Pipenbrinck

10
타이트한 루프에서이 루틴을 몇 백만 번 호출하지 않으면 성능에 전혀 신경 쓸 이유가 없으며 성능 손실이 무시할 수 있기 때문에 순진하지만 읽을 수있는 접근 방식을 사용할 수도 있습니다. 그리고 FWIW에서 8 비트 LUT는 10-20 번의 호출로 캐시 핫이됩니다.

6
나는 이것이 실제로 앱에서 무거운 리프팅을 수행하는 방법으로 만든 리프 호출 인 상황을 상상하기가 어렵다고 생각하지 않습니다. 무슨 일이 일어나고 있는지에 따라 더 작은 버전이 이길 수 있습니다. 더 나은 참조 지역성으로 인해 동료를 능가하는 많은 알고리즘이 작성되었습니다. 왜 이러지 않습니까?
Jason

clang을 사용하여 시도해보십시오 . 내장 구현에 훨씬 더 똑똑합니다.
Matt Joiner

3
GCC는 '옆으로 추가'보다 빠른 경우 -msse4.2로 호출하지 않으면 popcont 명령을 내 보내지 않습니다.
lvella

54

Java를 사용하는 경우 내장 메소드 Integer.bitCount가이를 수행합니다.


썬이 다른 API를 제공했을 때 백그라운드에서 일부 로직을 사용해야합니까?
Vallabh Patade

2
참고로 Java의 구현은 Kevin Little이 지적한 것과 동일한 알고리즘을 사용합니다 .
Marco Bolis

2
구현을 제외하고, 이것은 아마도 개발자가 코드를 유지 한 후 (또는 6 개월 후에 다시 돌아올 때) 가장 명확한 의도의 메시지 일 것입니다.
divillysausages

31
unsigned int count_bit(unsigned int x)
{
  x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
  x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
  x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
  x = (x & 0x0000FFFF) + ((x >> 16)& 0x0000FFFF);
  return x;
}

이 알고리즘을 설명하겠습니다.

이 알고리즘은 Divide and Conquer Algorithm을 기반으로합니다. 8 비트 정수 213 (이진수로 11010101)이 있다고 가정하면 알고리즘은 다음과 같이 작동합니다 (각 시간마다 두 개의 이웃 블록을 병합합니다).

+-------------------------------+
| 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |  <- x
|  1 0  |  0 1  |  0 1  |  0 1  |  <- first time merge
|    0 0 1 1    |    0 0 1 0    |  <- second time merge
|        0 0 0 0 0 1 0 1        |  <- third time ( answer = 00000101 = 5)
+-------------------------------+

7
이 알고리즘은 Matt Howells가 게시 한 버전으로, 읽을 수 없게 된 사실에 최적화되었습니다.
Lefteris E 2016

29

이것은 마이크로 아키텍처를 아는 데 도움이되는 질문 중 하나입니다. 방금 gcc 4.3.3에서 C ++ 인라인을 사용하여 -O3으로 컴파일 된 함수 변형 오버 헤드, 10 억 반복을 두 번 수정하여 컴파일러가 타이밍에 rdtsc를 사용하여 중요한 것을 제거하지 않도록 모든 카운트의 누적 합계를 유지했습니다 ( 정확한 클럭 사이클).

인라인 int pop2 (부호없는 x, 부호없는 y)
{
    x = x-((x >> 1) & 0x55555555);
    y = y-((y >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    y = (y & 0x33333333) + ((y >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    y = y + (y >> 8);
    x = x + (x >> 16);
    y = y + (y >> 16);
    리턴 (x + y) & 0x000000FF;
}

수정되지 않은 해커의 기쁨에는 12.2 기가 사이클이 걸렸습니다. 병렬 버전 (2 배 많은 비트 수)은 13.0 기가 사이클로 실행됩니다. 2.4GHz Core Duo에서 모두 10.5 초가 경과했습니다. 25 기가 사이클 =이 클럭 주파수에서 10 초 이상이므로 타이밍이 정확하다고 확신합니다.

이것은 명령 종속성 체인과 관련이 있으며이 알고리즘에는 매우 나쁩니다. 한 쌍의 64 비트 레지스터를 사용하여 속도를 거의 두 배로 늘릴 수 있습니다. 사실, 영리하고 x + ya를 조금 더 일찍 추가하면 약간의 변화를 줄일 수 있습니다. 약간의 미세 조정이 포함 된 64 비트 버전은 균등하게 나오지만 다시 두 배 많은 비트를 계산합니다.

128 비트 SIMD 레지스터, 또 다른 두 가지 요인으로 인해 SSE 명령어 세트에는 종종 지름길이 있습니다.

코드가 특별히 투명한 이유는 없습니다. 인터페이스는 간단하고 알고리즘은 여러 곳에서 온라인으로 참조 할 수 있으며 포괄적 인 단위 테스트가 가능합니다. 그것을 우연히 발견 한 프로그래머는 무언가를 배울 수도 있습니다. 이러한 비트 작업은 기계 수준에서 매우 자연 스럽습니다.

좋아, 나는 조정 된 64 비트 버전을 벤치로하기로 결정했습니다. 이 하나의 sizeof (부호없는 long) == 8

인라인 int pop2 (부호없는 긴 x, 부호없는 긴 y)
{
    x = x-((x >> 1) & 0x5555555555555555);
    y = y-((y >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    x & 0xFF를 반환;
}

그것은 옳은 것처럼 보입니다 (하지만 신중하게 테스트하지는 않습니다). 이제 타이밍은 10.70 기가 사이클 / 14.1 기가 사이클로 나옵니다. 그 이후의 숫자는 1,200 억 비트에 이르렀으며이 머신에서 경과 된 5.9 초에 해당합니다. 병렬이 아닌 버전은 64 비트 모드에서 실행 중이며 32 비트 레지스터보다 약간 더 나은 64 비트 레지스터를 좋아하기 때문에 약간의 속도가 빠릅니다.

여기에 더 많은 OOO 파이프 라이닝이 있는지 확인해 봅시다. 이것은 조금 더 복잡했기 때문에 실제로 약간 테스트했습니다. 각 항의 합계는 64이고 합산 된 합계는 256입니다.

inline int pop4 (부호없는 긴 x, 부호없는 긴 y, 
                부호없는 long u, 부호없는 long v)
{
  열거 형 {m1 = 0x5555555555555555, 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF};

    x = x-((x >> 1) & m1);
    y = y-((y >> 1) & m1);
    u = u-((u >> 1) & m1);
    v = v-((v >> 1) & m1);
    x = (x & m2) + ((x >> 2) & m2);
    y = (y & m2) + ((y >> 2) & m2);
    u = (u & m2) + ((u >> 2) & m2);
    v = (v & m2) + ((v >> 2) & m2);
    x = x + y; 
    u = u + v; 
    x = (x & m3) + ((x >> 4) & m3);
    u = (u & m3) + ((u >> 4) & m3);
    x = x + u; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x & m4; 
    x = x + (x >> 32);
    x & 0x000001FF를 반환;
}

나는 잠시 흥분했지만 gcc가 일부 테스트에서 인라인 키워드를 사용하지 않더라도 -O3으로 인라인 트릭을 재생하고 있음이 밝혀졌습니다. gcc가 트릭을 할 때 pop4 ()에 대한 10 억 호출에는 12.56 기가 사이클이 걸리지 만 인수를 상수 식으로 접는 것으로 결정했습니다. 더 현실적인 수치는 추가 30 % 속도 향상을 위해 19.6gc 인 것으로 보입니다. 내 테스트 루프는 이제 다음과 같이 보입니다. 각 인수가 gcc가 트릭을 연주하는 것을 막을만큼 충분히 달라야합니다.

   hitime b4 = rdtsc (); 
   (서명없는 i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) 
      합 + = pop4 (i, i ^ 1, ~ i, i | 1); 
   hitime e4 = rdtsc (); 

8.17 초에 합산 된 256 억 비트가 경과했습니다. 16 비트 테이블 조회에서 벤치 마크 된 32 백만 비트에 대해 1.02로 작동합니다. 다른 벤치는 클럭 속도를 제공하지 않기 때문에 직접 비교할 수는 없지만 64KB 테이블 에디션에서 코딱지를 때린 것 같습니다. 처음에는 L1 캐시를 비극적으로 사용합니다.

업데이트 : 명백한 작업을 수행하고 4 개의 중복 된 줄을 추가하여 pop6 ()을 만들기로 결정했습니다. 22.8gc로 나 왔으며, 9.5 초 동안 합산 된 3,384 억 비트가 경과했습니다. 이제 20 %가 800ms에서 32 억 비트에 이릅니다.


2
이와 같은 최고의 비 어셈블러 형식은 한 번에 24 개의 32 비트 단어를 전개 한 것을 보았습니다. dalkescientific.com/writings/diary/popcnt.c , stackoverflow.com/questions/3693981/... , dalkescientific.com/writings/diary/archive/2008/07/05/...
매트 소목

28

반복적으로 2로 나누지 않겠습니까?

카운트 = 0
n> 0 인 동안
  (n % 2) == 1 인 경우
    카운트 + = 1
  n / = 2  

나는 이것이 가장 빠르지는 않지만 "최고"는 다소 모호하다는 것에 동의합니다. 나는 "최고"가 명확성의 요소를 가져야한다고 주장하고있다


그것은 작동하고 이해하기 쉽지만 더 빠른 방법이 있습니다.
Matt Howells

2
이 작업을 많이 하지 않으면 성능에 미치는 영향은 미미합니다. 그래서 모든 것이 평등하다는 것을 나는 '최고'는 "괴물처럼 읽히지 않았다"는 다니엘과 동의합니다.

2
나는 다양한 방법을 얻기 위해 고의로 '최고'를 정의하지 않았습니다. 우리가 이런 종류의 비트 트위들 링 수준으로 내려 가면 침팬지가 타이핑 한 것처럼 보이는 빠른 속도의 무언가를 찾고있을 것입니다.
Matt Howells

6
잘못된 코드입니다. 컴파일러는 그것을 능숙하게 만들 수 있지만 내 테스트에서 GCC는 그렇지 않았습니다. (n % 2)를 (n & 1)으로 바꾸십시오. 그리고 MODULO보다 훨씬 빠릅니다. (n / = 2)를 (n >> = 1)로 바꾸십시오. 분할보다 훨씬 빠르게 비트 시프 팅.
Mecki

6
@Mecki : 내 테스트에서 gcc (4.0, -O3) 명백한 최적화를 수행했습니다.

26

비트 패턴을 작성할 때 해커의 기쁨 비트 비틀 링이 훨씬 더 명확 해집니다.

unsigned int bitCount(unsigned int x)
{
  x = ((x >> 1) & 0b01010101010101010101010101010101)
     + (x       & 0b01010101010101010101010101010101);
  x = ((x >> 2) & 0b00110011001100110011001100110011)
     + (x       & 0b00110011001100110011001100110011); 
  x = ((x >> 4) & 0b00001111000011110000111100001111)
     + (x       & 0b00001111000011110000111100001111); 
  x = ((x >> 8) & 0b00000000111111110000000011111111)
     + (x       & 0b00000000111111110000000011111111); 
  x = ((x >> 16)& 0b00000000000000001111111111111111)
     + (x       & 0b00000000000000001111111111111111); 
  return x;
}

첫 번째 단계는 짝수 비트를 홀수 비트에 추가하여 각 두 비트의 합을 생성합니다. 다른 단계는 고차 청크를 저차 청크에 추가하여 최종 카운트가 전체 int를 차지할 때까지 청크 크기를 두 배로 늘립니다.


3
이 솔루션은 운영자 우선 순위와 관련하여 사소한 문제가있는 것 같습니다. 각 용어에 대해 x = (((x >> 1) & 0b01010101010101010101010101010101) + (x & 0b01010101010101010101010101010101)); (즉, 여분의 괄호가 추가됨).
Nopik

21

2 32 개의 룩업 테이블과 각 비트를 개별적으로 반복 하는 행복한 매체 :

int bitcount(unsigned int num){
    int count = 0;
    static int nibblebits[] =
        {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
    for(; num != 0; num >>= 4)
        count += nibblebits[num & 0x0f];
    return count;
}

에서 http://ctips.pbwiki.com/CountBits


휴대용이 아닙니다. CPU에 9 비트 바이트가 있으면 어떻게됩니까? 그렇습니다. 실제 CPU는 저기 있습니다.
Robert S. Barnes

15
@Robert S. Barnes,이 기능은 여전히 ​​작동합니다. 기본 단어 크기에 대해서는 가정하지 않으며 "바이트"에 대한 참조는 전혀 없습니다.
finnw

19

이것은 수행 될 수있는 O(k)경우, k설정 비트 수이다.

int NumberOfSetBits(int n)
{
    int count = 0;

    while (n){
        ++ count;
        n = (n - 1) & n;
    }

    return count;
}

이것은 본질적으로 Brian Kernighan의 알고리즘 n &= (n-1)입니다.
Adrian Mole

17

가장 빠르거나 최상의 솔루션은 아니지만 내 방식으로 같은 질문을 발견하고 생각하고 생각하기 시작했습니다. 마지막으로 수학적 측면에서 문제를 얻고 그래프를 그리면 이와 같이 할 수 있다는 것을 깨달았습니다. 그러면 주기적 부분이있는 함수라는 것을 알았습니다. 그러면 기간의 차이를 깨닫게됩니다 ... 여기 있습니다 :

unsigned int f(unsigned int x)
{
    switch (x) {
        case 0:
            return 0;
        case 1:
            return 1;
        case 2:
            return 1;
        case 3:
            return 2;
        default:
            return f(x/4) + f(x%4);
    }
}

4
아 좋아. 파이썬 버전을 한판 승부하는 방법 :def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
언더런

10

찾고있는 기능을 종종 2 진 숫자의 "측면 합계"또는 "인구 수"라고합니다. Knuth는 Pre-Fascicle 1A, pp11-12에서 논의합니다 (볼륨 2, 4.6.3- (7)에 간단한 참조가 있었음에도 불구하고).

궤적 classicus이 로부터 피터 웨 그너의 기사 "이진 컴퓨터의 계산 방식 사람을위한 기술"이다 ACM의 커뮤니케이션 , 제 3 권 (1960) 번호 5, 322 페이지 . 그는 두 가지 다른 알고리즘을 제공하는데, 하나는 "약한"것으로 예상되는 숫자 (즉, 적은 수의 숫자)와 다른 경우에 최적화 된 숫자에 최적화되어 있습니다.


10
  private int get_bits_set(int v)
    {
      int c; // c accumulates the total bits set in v
        for (c = 0; v>0; c++)
        {
            v &= v - 1; // clear the least significant bit set
        }
        return c;
    }

9

몇 가지 공개 질문 :-

  1. 숫자가 음수이면?
  2. 숫자가 1024 인 경우 "반복적으로 2로 나누기"방법은 10 회 반복됩니다.

다음과 같이 음수를 지원하도록 알고리즘을 수정할 수 있습니다.

count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
    count += 1
  n /= 2  
return count

이제 두 번째 문제를 극복하기 위해 다음과 같이 algo를 작성할 수 있습니다.

int bit_count(int num)
{
    int count=0;
    while(num)
    {
        num=(num)&(num-1);
        count++;
    }
    return count;
}

자세한 내용은 다음을 참조하십시오.

http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html


9

Brian Kernighan의 방법도 유용 할 것이라고 생각합니다 . 설정된 비트가있는 한 많은 반복을 거칩니다. 따라서 높은 비트 세트 만 가진 32 비트 워드가있는 경우 루프를 한 번만 통과합니다.

int countSetBits(unsigned int n) { 
    unsigned int n; // count the number of bits set in n
    unsigned int c; // c accumulates the total bits set in n
    for (c=0;n>0;n=n&(n-1)) c++; 
    return c; 
}

1988 년에 출판 된 C Programming Language 2nd Ed. (Brian W. Kernighan과 Dennis M. Ritchie)는 운동 2-9에서 이것을 언급합니다. 2006 년 4 월 19 일, Don Knuth는이 방법이 Peter Wegner에 의해 CACM 3 (1960), 322에서 처음 출판되었다고 지적했다 (Derrick Lehmer에 의해 독립적으로 발견되어 1964 년에 Beckenbach에 의해 편집 된 책으로 출판 됨).


8

더 직관적 인 아래 코드를 사용합니다.

int countSetBits(int n) {
    return !n ? 0 : 1 + countSetBits(n & (n-1));
}

논리 : n & (n-1)은 n의 마지막 설정 비트를 재설정합니다.

추신 : 흥미로운 솔루션이지만 이것이 O (1) 솔루션이 아니라는 것을 알고 있습니다.


이는 비트 수가 적은 "스파 스"숫자에 유용합니다 O(ONE-BITS). 최대 32 개의 1 비트가 있으므로 실제로 O (1)입니다.
ealfonso

7

"최상의 알고리즘"이란 무엇입니까? 단축 코드 또는 단축 코드? 코드가 매우 우아해 보이고 실행 시간이 일정합니다. 코드도 매우 짧습니다.

그러나 속도가 코드 크기가 아닌 주요 요인이라면 다음과 같은 것이 더 빠를 수 있다고 생각합니다.

       static final int[] BIT_COUNT = { 0, 1, 1, ... 256 values with a bitsize of a byte ... };
        static int bitCountOfByte( int value ){
            return BIT_COUNT[ value & 0xFF ];
        }

        static int bitCountOfInt( int value ){
            return bitCountOfByte( value ) 
                 + bitCountOfByte( value >> 8 ) 
                 + bitCountOfByte( value >> 16 ) 
                 + bitCountOfByte( value >> 24 );
        }

64 비트 값의 경우 더 빠르지 않지만 32 비트 값은 빠를 수 있다고 생각합니다.


내 코드에는 10 가지 작업이 있습니다. 귀하의 코드에는 12 개의 작업이 있습니다. 귀하의 링크는 더 작은 배열 (5)에서 작동합니다. 256 개의 요소를 사용합니다. 캐싱을 사용하면 문제가 될 수 있습니다. 그러나 자주 사용하면 문제가되지 않습니다.
Horcrux7

이 접근법은 비트 트위들 링 접근법보다 상당히 빠릅니다. 더 많은 메모리를 사용하는 경우 더 적은 코드로 컴파일되며 함수를 인라인 할 때마다 그 이득이 반복됩니다. 따라서 쉽게 순 승리로 판명 될 수 있습니다.

7

나는 약 1990 년에 RISC 머신을위한 빠른 비트 카운트 매크로를 썼다. 그것은 고급 산술 (곱셈, 나눗셈, %), 메모리 페치 (너무 느리게), 분기 (너무 느리게)를 사용하지 않지만 CPU는 32 비트 배럴 시프터 (즉, >> 1과 >> 32는 같은 양의주기를 사용합니다.) 작은 상수 (예 : 6, 12, 24)는 레지스터에로드하는 데 비용이 들지 않거나 저장되는 것으로 가정합니다. 일시적으로 다시 반복해서 재사용했습니다.

이러한 가정을 통해 대부분의 RISC 시스템에서 약 16주기 / 지침으로 32 비트를 계산합니다. 15 개의 명령 / 사이클은 사이클 수 또는 명령 수의 하한에 가깝습니다. 왜냐하면 추가 횟수를 반으로 줄이려면 적어도 3 개의 명령 (마스크, 시프트, 연산자)이 필요하기 때문에 log_2 (32) = 5, 5 x 3 = 15 명령어는 준-하한입니다.

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

가장 복잡한 첫 번째 단계의 비밀은 다음과 같습니다.

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

위의 첫 번째 열 (A)을 가져 와서 1 비트 오른쪽으로 이동하고 AB에서 빼면 출력 (CD)을 얻습니다. 3 비트로의 확장은 비슷합니다. 원하는 경우 위의 광산과 같은 8 행 부울 테이블로 확인할 수 있습니다.

  • 돈 길리스

7

C ++를 사용하는 경우 다른 옵션은 템플릿 메타 프로그래밍을 사용하는 것입니다.

// recursive template to sum bits in an int
template <int BITS>
int countBits(int val) {
        // return the least significant bit plus the result of calling ourselves with
        // .. the shifted value
        return (val & 0x1) + countBits<BITS-1>(val >> 1);
}

// template specialisation to terminate the recursion when there's only one bit left
template<>
int countBits<1>(int val) {
        return val & 0x1;
}

사용법은 다음과 같습니다.

// to count bits in a byte/char (this returns 8)
countBits<8>( 255 )

// another byte (this returns 7)
countBits<8>( 254 )

// counting bits in a word/short (this returns 1)
countBits<16>( 256 )

물론이 템플릿을 더 확장하여 다른 유형 (자동 감지 비트 크기 포함)을 사용할 수도 있지만 명확성을 위해 단순하게 유지했습니다.

편집 : 이것은 C ++ 컴파일러에서 작동 해야 하기 때문에 좋은 점을 잊어 버렸습니다 . 비트 수에 상수 값이 사용되면 기본적으로 루프가 풀립니다 (즉, 가장 빠른 일반적인 방법이라고 확신합니다) 당신은 찾을 것이다)


불행하게도, 비트 카운팅은 병렬로 수행되지 않으므로 아마 느릴 수 있습니다. constexpr그래도 좋을 수도 있습니다.
imallett

동의-C ++ 템플릿 재귀에서 재미있는 연습 이었지만 확실히 순진한 솔루션이었습니다.
pentaphobe

6

특히 포춘 파일 에서이 예제를 좋아합니다.

# 비트 카운트 정의 (x) (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F) % 255)
#define BX_ (x) ((x)-(((x) >> 1) & 0x77777777)
                             -(((x) >> 2) & 0x33333333)
                             -(((x) >> 3) & 0x11111111))

너무 예뻐서 가장 좋아합니다!


1
다른 제안과 비교하여 어떻게 수행됩니까?
asdf

6

자바 JDK1.5

Integer.bitCount (n);

여기서 n은 1을 세는 숫자입니다.

또한 확인

Integer.highestOneBit(n);
Integer.lowestOneBit(n);
Integer.numberOfLeadingZeros(n);
Integer.numberOfTrailingZeros(n);

//Beginning with the value 1, rotate left 16 times
     n = 1;
         for (int i = 0; i < 16; i++) {
            n = Integer.rotateLeft(n, 1);
            System.out.println(n);
         }

실제로는 알고리즘이 아니라 라이브러리 호출 일뿐입니다. Java에는 유용하지만 다른 모든 사람들에게는 그다지 유용하지 않습니다.
benzado

2
@benzado는 옳지 만 어쨌든 +1이다. 일부 자바 개발자들은이 방법을 알지 못할 수도 있기 때문이다
finnw

@finnw, 나는 그 개발자 중 하나입니다. :)
neevek

6

SIMD 명령 (SSSE3 및 AVX2)을 사용하여 배열에서 비트 카운팅 구현을 발견했습니다. __popcnt64 내장 함수를 사용하는 것보다 성능이 2-2.5 배 향상되었습니다.

SSSE3 버전 :

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2 버전 :

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}

6

나는 항상 이것을 Competitive Programming에서 사용하며 작성하기 쉽고 효율적입니다.

#include <bits/stdc++.h>

using namespace std;

int countOnes(int n) {
    bitset<32> b(n);
    return b.count();
}

5

설정 비트를 계산하는 많은 알고리즘이 있습니다. 그러나 나는 가장 좋은 것이 더 빠르다고 생각합니다! 이 페이지에서 자세한 내용을 볼 수 있습니다.

비트 트위들 링 해킹

나는 이것을 제안한다 :

64 비트 명령어를 사용하여 14, 24 또는 32 비트 워드로 설정된 비트 수

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

// option 1, for at most 14-bit values in v:
c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

// option 2, for at most 24-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
     % 0x1f;

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

이 방법을 효율적으로 사용하려면 모듈러스 분할이 빠른 64 비트 CPU가 필요합니다. 첫 번째 옵션은 3 가지 작업 만 수행합니다. 두 번째 옵션은 10입니다. 세 번째 옵션은 15입니다.


5

입력 크기를 분기하여 미리 계산 된 바이트 비트 수 테이블을 사용하는 빠른 C # 솔루션.

public static class BitCount
{
    public static uint GetSetBitsCount(uint n)
    {
        var counts = BYTE_BIT_COUNTS;
        return n <= 0xff ? counts[n]
             : n <= 0xffff ? counts[n & 0xff] + counts[n >> 8]
             : n <= 0xffffff ? counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff]
             : counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff] + counts[(n >> 24) & 0xff];
    }

    public static readonly uint[] BYTE_BIT_COUNTS = 
    {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
    };
}

아이러니하게도,이 테이블은이 스레드에 게시 된 알고리즘에 의해 생성 될 수 있습니다! 그럼에도 불구하고 이와 같은 테이블을 사용한다는 것은 일정한 시간 성능을 의미합니다. 한 단계 더 나아가 64K 변환 표를 만들면 필요한 AND, SHIFT 및 ADD 작업이 절반으로 줄어 듭니다. 비트 매니퓰레이터의 흥미로운 주제!
user924272

캐시 문제로 인해 더 큰 테이블이 느려질 수 있습니다 (상시 시간이 아님). (0xe994 >>(k*2))&3메모리 액세스없이 한 번에 3 비트를 '찾을'수 있습니다 .
greggo

5

다음은 모든 아키텍처에서 각 알고리즘을 벤치마킹 할 수있는 휴대용 모듈 (ANSI-C)입니다.

CPU에 9 비트 바이트가 있습니까? 문제 없음 :-) 현재 2 개의 알고리즘, K & R 알고리즘 및 바이트 단위 조회 테이블을 구현합니다. 조회 테이블은 K & R 알고리즘보다 평균 3 배 빠릅니다. 누군가 "해커 딜라이트 (Hacker 's Delight)"알고리즘을 이식 가능하게 만드는 방법을 알아낼 수 있다면

#ifndef _BITCOUNT_H_
#define _BITCOUNT_H_

/* Return the Hamming Wieght of val, i.e. the number of 'on' bits. */
int bitcount( unsigned int );

/* List of available bitcount algorithms.  
 * onTheFly:    Calculate the bitcount on demand.
 *
 * lookupTalbe: Uses a small lookup table to determine the bitcount.  This
 * method is on average 3 times as fast as onTheFly, but incurs a small
 * upfront cost to initialize the lookup table on the first call.
 *
 * strategyCount is just a placeholder. 
 */
enum strategy { onTheFly, lookupTable, strategyCount };

/* String represenations of the algorithm names */
extern const char *strategyNames[];

/* Choose which bitcount algorithm to use. */
void setStrategy( enum strategy );

#endif

.

#include <limits.h>

#include "bitcount.h"

/* The number of entries needed in the table is equal to the number of unique
 * values a char can represent which is always UCHAR_MAX + 1*/
static unsigned char _bitCountTable[UCHAR_MAX + 1];
static unsigned int _lookupTableInitialized = 0;

static int _defaultBitCount( unsigned int val ) {
    int count;

    /* Starting with:
     * 1100 - 1 == 1011,  1100 & 1011 == 1000
     * 1000 - 1 == 0111,  1000 & 0111 == 0000
     */
    for ( count = 0; val; ++count )
        val &= val - 1;

    return count;
}

/* Looks up each byte of the integer in a lookup table.
 *
 * The first time the function is called it initializes the lookup table.
 */
static int _tableBitCount( unsigned int val ) {
    int bCount = 0;

    if ( !_lookupTableInitialized ) {
        unsigned int i;
        for ( i = 0; i != UCHAR_MAX + 1; ++i )
            _bitCountTable[i] =
                ( unsigned char )_defaultBitCount( i );

        _lookupTableInitialized = 1;
    }

    for ( ; val; val >>= CHAR_BIT )
        bCount += _bitCountTable[val & UCHAR_MAX];

    return bCount;
}

static int ( *_bitcount ) ( unsigned int ) = _defaultBitCount;

const char *strategyNames[] = { "onTheFly", "lookupTable" };

void setStrategy( enum strategy s ) {
    switch ( s ) {
    case onTheFly:
        _bitcount = _defaultBitCount;
        break;
    case lookupTable:
        _bitcount = _tableBitCount;
        break;
    case strategyCount:
        break;
    }
}

/* Just a forwarding function which will call whichever version of the
 * algorithm has been selected by the client 
 */
int bitcount( unsigned int val ) {
    return _bitcount( val );
}

#ifdef _BITCOUNT_EXE_

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

/* Use the same sequence of pseudo random numbers to benmark each Hamming
 * Weight algorithm.
 */
void benchmark( int reps ) {
    clock_t start, stop;
    int i, j;
    static const int iterations = 1000000;

    for ( j = 0; j != strategyCount; ++j ) {
        setStrategy( j );

        srand( 257 );

        start = clock(  );

        for ( i = 0; i != reps * iterations; ++i )
            bitcount( rand(  ) );

        stop = clock(  );

        printf
            ( "\n\t%d psudoe-random integers using %s: %f seconds\n\n",
              reps * iterations, strategyNames[j],
              ( double )( stop - start ) / CLOCKS_PER_SEC );
    }
}

int main( void ) {
    int option;

    while ( 1 ) {
        printf( "Menu Options\n"
            "\t1.\tPrint the Hamming Weight of an Integer\n"
            "\t2.\tBenchmark Hamming Weight implementations\n"
            "\t3.\tExit ( or cntl-d )\n\n\t" );

        if ( scanf( "%d", &option ) == EOF )
            break;

        switch ( option ) {
        case 1:
            printf( "Please enter the integer: " );
            if ( scanf( "%d", &option ) != EOF )
                printf
                    ( "The Hamming Weight of %d ( 0x%X ) is %d\n\n",
                      option, option, bitcount( option ) );
            break;
        case 2:
            printf
                ( "Please select number of reps ( in millions ): " );
            if ( scanf( "%d", &option ) != EOF )
                benchmark( option );
            break;
        case 3:
            goto EXIT;
            break;
        default:
            printf( "Invalid option\n" );
        }

    }

 EXIT:
    printf( "\n" );

    return 0;
}

#endif

1
나는 재사용 가능한 라이브러리 또는 독립 실행 형 테스트 실행 파일로 빌드하는 스위치뿐만 아니라 플러그인, 다형성 접근 방식을 매우 좋아합니다. 매우 잘 생각 =)

5

당신이 할 수있는 것은

while(n){
    n=n&(n-1);
    count++;
}

이것 뒤에 논리는 n-1의 비트가 n의 가장 오른쪽 세트 비트에서 반전된다는 것입니다. n = 6, 즉 110이면 5는 101이면, 비트는 n의 가장 오른쪽에 설정된 비트로부터 반전된다. 따라서 우리 &이 두 가지 경우 우리는 모든 반복에서 가장 오른쪽 비트 0을 만들고 항상 다음 가장 오른쪽 세트 비트로 이동합니다.

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