숫자 7을 나타내는 8 비트는 다음과 같습니다.
00000111
3 비트가 설정됩니다.
32 비트 정수에서 설정 비트 수를 결정하는 알고리즘은 무엇입니까?
숫자 7을 나타내는 8 비트는 다음과 같습니다.
00000111
3 비트가 설정됩니다.
32 비트 정수에서 설정 비트 수를 결정하는 알고리즘은 무엇입니까?
답변:
이를 ' 해밍 가중치 ', '인수'또는 '옆으로 추가'라고합니다.
'최상의'알고리즘은 실제로 사용중인 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);
이것은 논의 된 알고리즘 중 최악의 최악의 동작을 가지므로 사용자가 사용하는 사용 패턴이나 값을 효율적으로 처리합니다.
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)) & 0x0F0F0F0F
4 배속 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가 사용합니다 . 이를 위해 내장 또는 내장 함수를 사용할 수있는 경우 컴파일러에서 대상별 최적화를 수행 할 수있는 기회를 제공하십시오.
이 비트 -SWAR 알고리즘은 SIMD는 있지만 사용 가능한 팝 카운트 명령이없는 CPU의 속도를 높이기 위해 단일 정수 레지스터 대신 여러 벡터 요소에서 한 번에 병렬 처리되도록 병렬 처리 할 수 있습니다. (예 : Nehalem 이상이 아닌 모든 CPU에서 실행되어야하는 x86-64 코드)
그러나 popcount에 벡터 명령어를 사용하는 가장 좋은 방법은 일반적으로 variable-shuffle을 사용하여 각 바이트의 시간에 4 비트 씩 테이블 조회를 병렬로 수행하는 것입니다. (4 비트는 벡터 레지스터에 보유 된 16 개의 엔트리 테이블을 인덱스합니다).
Intel CPU에서 하드웨어 64 비트 popcnt 명령은 SSSE3 PSHUFB
비트 병렬 구현 을 2 배 정도 수행 할 수 있지만 컴파일러에서 올바르게 얻는 경우 에만 가능합니다 . 그렇지 않으면 SSE가 크게 앞설 수 있습니다. 최신 컴파일러 버전은 인텔 의 popcnt 잘못된 종속성 문제를 알고 있습니다.
참고 문헌 :
unsigned int
부호 비트 합병증이 없음을 쉽게 표시하기 위해을 사용해야 할 수도 있습니다. 또한 uint32_t
모든 플랫폼에서 기대하는 것을 얻는 것처럼 더 안전합니까?
>>
음수 값에 대한 구현 정의입니다. 인수는로 변경 (또는 캐스트)해야 unsigned
하며 코드는 32 비트에 따라 다르므로 아마도을 사용해야합니다 uint32_t
.
또한 컴파일러의 내장 함수를 고려하십시오.
예를 들어 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:AVX
popcnt
그러나 적어도 어느 곳에서나 휴대 가능한 것을 얻을 수 있으며 올바른 대상 옵션이있는 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
방출합니다 ( int
arg 버전의 경우).
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 에게는 좋지 않습니다 .
std::bitset::count
. 인라인 후 단일 __builtin_popcount
호출로 컴파일됩니다 .
제 생각에는 "최고의"솔루션은 다른 프로그래머 (또는 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);
}
이들은 특정 데이터 유형 크기에 의존하기 때문에 이식성이 떨어집니다. 그러나 많은 성능 최적화가 어쨌든 이식 가능하지 않기 때문에 문제가되지 않을 수 있습니다. 이식성을 원한다면 읽기 쉬운 솔루션을 고수하겠습니다.
if ((value & 1) == 1) { count++; }
로를 count += value & 1
?
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 명령어로 실행 (아치에 따라 다름), 분기 없음.
해커의 기쁨 은 유쾌합니다! 추천.
Integer.bitCount(int)
는 이와 동일한 정확한 구현을 사용합니다.
pop
대신해 population_count
(또는 pop_cnt
약어가 필요한 경우) 누군가에게 좋은 발걸음을 줄 것입니다. @MarcoBolis Java의 모든 버전에 해당되는 것으로 가정하지만 공식적으로 구현에 따라 다릅니다.)
조회 테이블과 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);
두 비트의 비트 수 0b00
는 0b01
또는 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"를 제안하십시오.
popcount(int v)
및 모두에 대해 작동한다는 것 popcount(unsigned v)
입니다. 이식성을 위해, popcount(uint32_t v)
* 0x1010101 부분과 같은 것을 고려 하십시오.
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
따라서 실제로 수행중인 작업을 확인하기 위해 문자를 계산할 필요가 없습니다 (첫 번째를 버린 이후 0
실수로 잘못된 비트 패턴을 마스크로 사용했다고 생각합니다) -7 글자 만 있고 8 글자는 없다는 것을 알기 전까지는 말입니다.
지루 해졌고 세 가지 접근 방식을 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을 소비하기에 충분하지 않은 경우 두 번째 방법을 사용하십시오. 그렇지 않으면 한 번에 한 비트 씩 읽기 가능하지만 느리게 접근하십시오.
비트 트위들 링 접근법을 사용하려는 상황을 생각하기는 어렵습니다.
편집 : 비슷한 결과는 여기 .
Java를 사용하는 경우 내장 메소드 Integer.bitCount
가이를 수행합니다.
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)
+-------------------------------+
이것은 마이크로 아키텍처를 아는 데 도움이되는 질문 중 하나입니다. 방금 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로 나누지 않겠습니까?
카운트 = 0 n> 0 인 동안 (n % 2) == 1 인 경우 카운트 + = 1 n / = 2
나는 이것이 가장 빠르지는 않지만 "최고"는 다소 모호하다는 것에 동의합니다. 나는 "최고"가 명확성의 요소를 가져야한다고 주장하고있다
비트 패턴을 작성할 때 해커의 기쁨 비트 비틀 링이 훨씬 더 명확 해집니다.
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를 차지할 때까지 청크 크기를 두 배로 늘립니다.
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;
}
이것은 수행 될 수있는 O(k)
경우, k
설정 비트 수이다.
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
n &= (n-1)
입니다.
가장 빠르거나 최상의 솔루션은 아니지만 내 방식으로 같은 질문을 발견하고 생각하고 생각하기 시작했습니다. 마지막으로 수학적 측면에서 문제를 얻고 그래프를 그리면 이와 같이 할 수 있다는 것을 깨달았습니다. 그러면 주기적 부분이있는 함수라는 것을 알았습니다. 그러면 기간의 차이를 깨닫게됩니다 ... 여기 있습니다 :
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);
}
}
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))()
찾고있는 기능을 종종 2 진 숫자의 "측면 합계"또는 "인구 수"라고합니다. Knuth는 Pre-Fascicle 1A, pp11-12에서 논의합니다 (볼륨 2, 4.6.3- (7)에 간단한 참조가 있었음에도 불구하고).
궤적 classicus이 로부터 피터 웨 그너의 기사 "이진 컴퓨터의 계산 방식 사람을위한 기술"이다 ACM의 커뮤니케이션 , 제 3 권 (1960) 번호 5, 322 페이지 . 그는 두 가지 다른 알고리즘을 제공하는데, 하나는 "약한"것으로 예상되는 숫자 (즉, 적은 수의 숫자)와 다른 경우에 최적화 된 숫자에 최적화되어 있습니다.
몇 가지 공개 질문 :-
다음과 같이 음수를 지원하도록 알고리즘을 수정할 수 있습니다.
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
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에 의해 편집 된 책으로 출판 됨).
"최상의 알고리즘"이란 무엇입니까? 단축 코드 또는 단축 코드? 코드가 매우 우아해 보이고 실행 시간이 일정합니다. 코드도 매우 짧습니다.
그러나 속도가 코드 크기가 아닌 주요 요인이라면 다음과 같은 것이 더 빠를 수 있다고 생각합니다.
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 비트 값은 빠를 수 있다고 생각합니다.
나는 약 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 행 부울 테이블로 확인할 수 있습니다.
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
그래도 좋을 수도 있습니다.
자바 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);
}
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];
}
설정 비트를 계산하는 많은 알고리즘이 있습니다. 그러나 나는 가장 좋은 것이 더 빠르다고 생각합니다! 이 페이지에서 자세한 내용을 볼 수 있습니다.
나는 이것을 제안한다 :
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입니다.
입력 크기를 분기하여 미리 계산 된 바이트 비트 수 테이블을 사용하는 빠른 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
};
}
(0xe994 >>(k*2))&3
메모리 액세스없이 한 번에 3 비트를 '찾을'수 있습니다 .
다음은 모든 아키텍처에서 각 알고리즘을 벤치마킹 할 수있는 휴대용 모듈 (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
당신이 할 수있는 것은
while(n){
n=n&(n-1);
count++;
}
이것 뒤에 논리는 n-1의 비트가 n의 가장 오른쪽 세트 비트에서 반전된다는 것입니다. n = 6, 즉 110이면 5는 101이면, 비트는 n의 가장 오른쪽에 설정된 비트로부터 반전된다. 따라서 우리 &이 두 가지 경우 우리는 모든 반복에서 가장 오른쪽 비트 0을 만들고 항상 다음 가장 오른쪽 세트 비트로 이동합니다.