8 bit đại diện cho số 7 trông như thế này:
00000111
Ba bit được đặt.
Các thuật toán để xác định số lượng bit thiết lập trong một số nguyên 32 bit là gì?
8 bit đại diện cho số 7 trông như thế này:
00000111
Ba bit được đặt.
Các thuật toán để xác định số lượng bit thiết lập trong một số nguyên 32 bit là gì?
Câu trả lời:
Điều này được gọi là ' Trọng lượng Hamming ', 'dân số' hoặc 'bổ sung sang một bên'.
Thuật toán 'tốt nhất' thực sự phụ thuộc vào CPU bạn đang sử dụng và kiểu sử dụng của bạn là gì.
Một số CPU có một lệnh dựng sẵn để thực hiện và một số khác có các lệnh song song hoạt động trên các vectơ bit. Các hướng dẫn song song (như x86 popcnt
, trên CPU có hỗ trợ) gần như chắc chắn sẽ nhanh nhất. Một số kiến trúc khác có thể có một lệnh chậm được triển khai với một vòng lặp được mã hóa để kiểm tra một bit trên mỗi chu kỳ ( cần dẫn nguồn ).
Phương pháp tra cứu bảng được điền trước có thể rất nhanh nếu CPU của bạn có bộ đệm lớn và / hoặc bạn đang thực hiện nhiều hướng dẫn này trong một vòng lặp chặt chẽ. Tuy nhiên, nó có thể bị ảnh hưởng do chi phí của 'lỗi bộ nhớ cache', trong đó CPU phải lấy một số bảng từ bộ nhớ chính. (Tra cứu từng byte riêng biệt để giữ cho bảng nhỏ.)
Nếu bạn biết rằng byte của bạn sẽ chủ yếu là 0 hoặc chủ yếu là 1 thì có các thuật toán rất hiệu quả cho các kịch bản này.
Tôi tin rằng một thuật toán có mục đích chung rất tốt là như sau, được gọi là 'thuật toán SWAR song song' hoặc 'độ chính xác biến'. Tôi đã diễn đạt điều này bằng ngôn ngữ giả giống như C, bạn có thể cần điều chỉnh nó để hoạt động với một ngôn ngữ cụ thể (ví dụ: sử dụng uint32_t cho C ++ và >>> trong 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;
}
Đối với JavaScript: ép buộc thành số nguyên với |0
hiệu suất: thay đổi dòng đầu tiên thànhi = (i|0) - ((i >> 1) & 0x55555555);
Điều này có hành vi trong trường hợp xấu nhất tốt nhất trong số các thuật toán được thảo luận, do đó sẽ xử lý hiệu quả với bất kỳ mô hình sử dụng hoặc giá trị nào bạn ném vào nó.
i = i - ((i >> 1) & 0x55555555);
Bước đầu tiên là một phiên bản tối ưu hóa của mặt nạ để cô lập các bit lẻ / chẵn, chuyển sang sắp xếp chúng và thêm. Điều này thực sự có 16 bổ sung riêng biệt trong các bộ tích lũy 2 bit ( SWAR = SIMD trong một thanh ghi ). Thích (i & 0x55555555) + ((i>>1) & 0x55555555)
.
Bước tiếp theo lấy số lẻ / thậm chí tám trong số các bộ tích lũy 16x 2 bit đó và thêm một lần nữa, tạo ra các khoản tiền 4 bit 4 bit. Việc i - ...
tối ưu hóa không thể thực hiện được trong lần này vì vậy nó chỉ che dấu trước / sau khi chuyển. Sử dụng cùng một 0x33...
hằng số cả hai lần thay vì 0xccc...
trước khi dịch chuyển là một điều tốt khi biên dịch cho các ISA cần xây dựng các hằng số 32 bit trong các thanh ghi riêng biệt.
Bước dịch chuyển và thêm cuối cùng của việc (i + (i >> 4)) & 0x0F0F0F0F
mở rộng tới các bộ tích lũy 8 bit 8 bit. Nó che dấu sau khi thêm thay vì trước đó, bởi vì giá trị tối đa trong bất kỳ bộ tích lũy 4 bit nào là 4
, nếu tất cả 4 bit của các bit đầu vào tương ứng được đặt. 4 + 4 = 8 vẫn phù hợp với 4 bit, do đó, việc mang giữa các phần tử nibble là không thể i + (i >> 4)
.
Cho đến nay, đây chỉ là SIMD khá bình thường sử dụng các kỹ thuật SWAR với một vài tối ưu hóa thông minh. Tiếp tục với cùng một mẫu cho 2 bước nữa có thể mở rộng lên gấp 2 lần 16 bit sau đó tăng gấp đôi số lượng 32 bit. Nhưng có một cách hiệu quả hơn trên các máy có nhân phần cứng nhanh:
Khi chúng ta có đủ "yếu tố", một bội số với hằng số ma thuật có thể tổng hợp tất cả các yếu tố thành yếu tố hàng đầu . Trong trường hợp này các phần tử byte. Nhân lên được thực hiện bằng cách dịch chuyển trái và thêm, do đó, bội số x * 0x01010101
kết quả trong x + (x<<8) + (x<<16) + (x<<24)
. Các phần tử 8 bit của chúng tôi đủ rộng (và giữ số lượng đủ nhỏ) mà phần tử này không tạo ra mang theo 8 bit hàng đầu đó.
Phiên bản 64 bit này có thể thực hiện các phần tử 8 bit 8 bit trong số nguyên 64 bit với số nhân 0x0101010101010101 và trích xuất byte cao bằng >>56
. Vì vậy, nó không thực hiện bất kỳ bước bổ sung, chỉ là hằng số rộng hơn. Đây là những gì GCC sử dụng cho __builtin_popcountll
các hệ thống x86 khi popcnt
hướng dẫn phần cứng không được bật. Nếu bạn có thể sử dụng nội trang hoặc nội tại cho việc này, hãy làm như vậy để cung cấp cho trình biên dịch cơ hội thực hiện tối ưu hóa cụ thể theo mục tiêu.
Thuật toán bitwise-SWAR này có thể song song được thực hiện trong nhiều phần tử vectơ cùng một lúc, thay vì trong một thanh ghi số nguyên duy nhất, để tăng tốc cho CPU với SIMD nhưng không có lệnh popcount có thể sử dụng được. (ví dụ mã x86-64 phải chạy trên bất kỳ CPU nào, không chỉ Nehalem trở lên.)
Tuy nhiên, cách tốt nhất để sử dụng các hướng dẫn vectơ cho popcount thường là sử dụng biến ngẫu nhiên để thực hiện tra cứu bảng cho 4 bit tại một thời điểm của mỗi byte song song. (4 bit lập chỉ mục một bảng nhập 16 được giữ trong một thanh ghi vector).
Trên CPU Intel, hướng dẫn popcnt 64 bit phần cứng có thể vượt trội so với triển khai song song bit SSSE3PSHUFB
khoảng 2 lần, nhưng chỉ khi trình biên dịch của bạn thực hiện đúng . Nếu không SSE có thể đi ra đáng kể phía trước. Các phiên bản trình biên dịch mới hơn nhận thức được vấn đề phụ thuộc sai popcnt trên Intel .
Người giới thiệu:
unsigned int
, để dễ dàng cho thấy rằng nó không có bất kỳ biến chứng bit dấu hiệu nào. Cũng sẽ uint32_t
an toàn hơn, như trong, bạn có nhận được những gì bạn mong đợi trên tất cả các nền tảng?
>>
được xác định thực hiện cho các giá trị âm. Đối số cần được thay đổi (hoặc truyền) thành unsigned
và vì mã này là 32 bit cụ thể, nên có lẽ nên sử dụng uint32_t
.
Cũng xem xét các chức năng tích hợp trong trình biên dịch của bạn.
Trên trình biên dịch GNU chẳng hạn, bạn chỉ có thể sử dụng:
int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);
Trong trường hợp xấu nhất, trình biên dịch sẽ tạo một cuộc gọi đến một hàm. Trong trường hợp tốt nhất, trình biên dịch sẽ phát ra một lệnh cpu để thực hiện cùng một công việc nhanh hơn.
Nội tại GCC thậm chí hoạt động trên nhiều nền tảng. Popcount sẽ trở thành xu hướng chủ đạo trong kiến trúc x86, vì vậy thật hợp lý khi bắt đầu sử dụng nội tại ngay bây giờ. Các kiến trúc khác có số lượng nhiều năm.
Trên x86, bạn có thể nói với trình biên dịch rằng nó có thể đảm nhận hỗ trợ cho popcnt
hướng dẫn bằng -mpopcnt
hoặc -msse4.2
cũng cho phép các hướng dẫn vectơ được thêm vào trong cùng một thế hệ. Xem các tùy chọn GCC x86 . -march=nehalem
(hoặc -march=
bất cứ CPU nào bạn muốn mã của mình giả định và điều chỉnh cho) có thể là một lựa chọn tốt. Chạy nhị phân kết quả trên CPU cũ hơn sẽ dẫn đến lỗi hướng dẫn bất hợp pháp.
Để tối ưu hóa nhị phân cho máy bạn xây dựng, hãy sử dụng -march=native
(với gcc, clang hoặc ICC).
MSVC cung cấp một nội tại cho x86 popcnt
hướng dẫn , nhưng không giống như gcc, nó thực sự là một nội tại cho hướng dẫn phần cứng và yêu cầu hỗ trợ phần cứng.
Sử dụng std::bitset<>::count()
thay vì tích hợp
Về lý thuyết, bất kỳ trình biên dịch nào biết cách tính toán hiệu quả cho CPU mục tiêu đều phải thể hiện chức năng đó thông qua ISO C ++ std::bitset<>
. Trong thực tế, bạn có thể tốt hơn với bit-hack AND / shift / ADD trong một số trường hợp đối với một số CPU mục tiêu.
Đối với các kiến trúc đích trong đó popcount phần cứng là một phần mở rộng tùy chọn (như x86), không phải tất cả các trình biên dịch đều có một std::bitset
lợi thế của nó khi có sẵn. Ví dụ: MSVC không có cách nào để kích hoạt popcnt
hỗ trợ tại thời gian biên dịch và luôn sử dụng tra cứu bảng , ngay cả với/Ox /arch:AVX
(ngụ ý SSE4.2, mặc dù về mặt kỹ thuật có một bit tính năng riêng cho popcnt
.)
Nhưng ít nhất bạn có được thứ gì đó di động hoạt động ở mọi nơi và với gcc / clang với các tùy chọn mục tiêu phù hợp, bạn sẽ có được số lượng phần cứng cho các kiến trúc hỗ trợ nó.
#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();
}
Xem asm từ gcc, clang, icc và MSVC trên trình thám hiểm trình biên dịch Godbolt.
x86-64 gcc -O3 -std=gnu++11 -mpopcnt
phát ra điều này:
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
phát ra (đối với int
phiên bản arg):
rldicl 3,3,0,32 # zero-extend from 32 to 64-bit
popcntd 3,3 # popcount
blr
Nguồn này hoàn toàn không dành riêng cho x86 hoặc GNU, nhưng chỉ biên dịch tốt cho x86 với gcc / clang / icc.
Cũng lưu ý rằng dự phòng của gcc cho các kiến trúc không có số đếm đơn hướng dẫn là một tra cứu bảng theo thời gian. Điều này không phải là tuyệt vời cho ARM, ví dụ .
std::bitset::count
. sau khi nội tuyến này biên dịch thành một __builtin_popcount
cuộc gọi duy nhất .
Theo tôi, giải pháp "tốt nhất" là giải pháp có thể được đọc bởi một lập trình viên khác (hoặc lập trình viên gốc hai năm sau đó) mà không cần bình luận nhiều. Bạn cũng có thể muốn giải pháp nhanh nhất hoặc thông minh nhất mà một số người đã cung cấp nhưng tôi thích khả năng đọc hơn sự thông minh bất cứ lúc nào.
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;
}
Nếu bạn muốn có thêm tốc độ (và giả sử bạn tài liệu tốt để giúp đỡ những người kế nhiệm), bạn có thể sử dụng tra cứu bảng:
// 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);
}
Mặc dù chúng dựa trên kích thước loại dữ liệu cụ thể để chúng không di động. Nhưng, vì nhiều tối ưu hóa hiệu suất không có khả năng di động, điều đó có thể không phải là vấn đề. Nếu bạn muốn tính di động, tôi sẽ sử dụng giải pháp có thể đọc được.
if ((value & 1) == 1) { count++; }
với count += value & 1
?
Từ Delight Delight, p. 66, Hình 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;
}
Thực hiện trong ~ 20-ish hướng dẫn (phụ thuộc vòm), không phân nhánh.
Delight của hacker là thú vị! Rất khuyến khích.
Integer.bitCount(int)
sử dụng chính việc thực hiện này.
pop
thay vì population_count
(hoặc pop_cnt
nếu bạn phải có một sự từ bỏ). @MarcoBolis Tôi cho rằng điều đó sẽ đúng với tất cả các phiên bản Java, nhưng chính thức sẽ phụ thuộc vào việc triển khai :)
Tôi nghĩ cách nhanh nhất mà không cần sử dụng bảng tra cứu và bảng xếp hạng popisount sau đây. Nó đếm các bit thiết lập chỉ với 12 thao tác.
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;
}
Nó hoạt động vì bạn có thể đếm tổng số bit được đặt bằng cách chia thành hai nửa, đếm số bit được đặt trong cả hai nửa và sau đó thêm chúng lên. Cũng biết như Divide and Conquer
mô thức. Chúng ta hãy đi vào chi tiết ..
v = v - ((v >> 1) & 0x55555555);
Số lượng bit trong hai bit có thể 0b00
, 0b01
hoặc 0b10
. Hãy thử làm việc này trên 2 bit ..
---------------------------------------------
| v | (v >> 1) & 0b0101 | v - x |
---------------------------------------------
0b00 0b00 0b00
0b01 0b00 0b01
0b10 0b01 0b01
0b11 0b01 0b10
Đây là những gì được yêu cầu: cột cuối cùng hiển thị số bit được đặt trong mỗi cặp hai bit. Nếu số hai bit >= 2 (0b10)
sau đó được and
tạo ra 0b01
, thì nó sẽ tạo ra 0b00
.
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
Câu nói này phải dễ hiểu. Sau thao tác đầu tiên, chúng ta có số bit được đặt trong mỗi hai bit, bây giờ chúng ta tổng hợp số đếm đó trong mỗi 4 bit.
v & 0b00110011 //masks out even two bits
(v >> 2) & 0b00110011 // masks out odd two bits
Sau đó, chúng tôi tổng hợp kết quả trên, cung cấp cho chúng tôi tổng số bit được đặt trong 4 bit. Tuyên bố cuối cùng là khó khăn nhất.
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
Hãy phá vỡ nó thêm nữa ...
v + (v >> 4)
Nó tương tự như tuyên bố thứ hai; thay vào đó, chúng tôi đang đếm các bit thiết lập trong nhóm 4. Chúng tôi biết rất nhiều vì các hoạt động trước đây của chúng tôi, đó là mọi hoạt động đều có số bit được đặt trong đó. Hãy xem một ví dụ. Giả sử chúng ta có byte 0b01000010
. Nó có nghĩa là nibble đầu tiên có bộ 4 bit và cái thứ hai có bộ 2 bit. Bây giờ chúng ta thêm những cái đó vào nhau.
0b01000010 + 0b01000000
Nó cung cấp cho chúng ta số bit thiết lập trong một byte, trong lần đầu tiên 0b01100010
và do đó chúng ta che dấu bốn byte cuối cùng của tất cả các byte trong số (loại bỏ chúng).
0b01100010 & 0xF0 = 0b01100000
Bây giờ mỗi byte có số bit thiết lập trong đó. Chúng ta cần thêm tất cả chúng lại với nhau. Bí quyết là nhân kết quả 0b10101010
có một thuộc tính thú vị. Nếu số của chúng tôi có bốn byte, A B C D
nó sẽ dẫn đến một số mới với các byte này A+B+C+D B+C+D C+D D
. Một số 4 byte có thể có tối đa 32 bit được đặt, có thể được biểu diễn dưới dạng 0b00100000
.
Tất cả những gì chúng ta cần bây giờ là byte đầu tiên có tổng của tất cả các bit được đặt trong tất cả các byte và chúng ta có được nó >> 24
. Thuật toán này được thiết kế cho các 32 bit
từ nhưng có thể dễ dàng sửa đổi cho các 64 bit
từ.
c =
vậy? Hình như là nên loại bỏ. Hơn nữa, đề xuất thêm một bộ paren A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" để tránh một số cảnh báo cổ điển.
popcount(int v)
và popcount(unsigned v)
. Đối với tính di động, hãy xem xét popcount(uint32_t v)
, vv Thực sự thích phần * 0x1010101.
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
vì vậy chúng tôi không cần phải đếm các chữ cái để xem những gì bạn đang làm (vì bạn đã loại bỏ dòng đầu tiên 0
, tôi vô tình nghĩ rằng bạn đã sử dụng mô hình bit sai (lật) làm mặt nạ - đó là cho đến khi tôi lưu ý chỉ có 7 chữ cái chứ không phải 8).
Tôi đã chán, và hẹn giờ một tỷ lần lặp của ba cách tiếp cận. Trình biên dịch là gcc -O3. CPU là bất cứ thứ gì họ đặt trong Macbook Pro thế hệ 1.
Nhanh nhất là như sau, ở mức 3,7 giây:
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] );
}
Vị trí thứ hai thuộc về cùng một mã nhưng tìm kiếm 4 byte thay vì 2 Halfwords. Điều đó mất khoảng 5,5 giây.
Vị trí thứ ba thuộc về cách tiếp cận 'bổ sung ngang' đôi chút, mất 8,6 giây.
Vị trí thứ tư thuộc về __builtin_popcount () của GCC, trong 11 giây đáng xấu hổ.
Cách tiếp cận đếm từng bit một lúc chậm hơn và tôi cảm thấy chán khi chờ đợi nó hoàn thành.
Vì vậy, nếu bạn quan tâm đến hiệu suất trên hết, hãy sử dụng phương pháp đầu tiên. Nếu bạn quan tâm, nhưng không đủ để chi 64Kb RAM cho nó, hãy sử dụng phương pháp thứ hai. Mặt khác, sử dụng phương pháp một bit có thể đọc được (nhưng chậm).
Thật khó để nghĩ về một tình huống mà bạn muốn sử dụng phương pháp xoay vòng một chút.
Chỉnh sửa: Kết quả tương tự ở đây .
Nếu bạn tình cờ sử dụng Java, phương thức dựng sẵn Integer.bitCount
sẽ làm điều đó.
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;
}
Hãy để tôi giải thích thuật toán này.
Thuật toán này dựa trên thuật toán phân chia và chinh phục. Giả sử có một số nguyên 8 bit 213 (11010101 ở dạng nhị phân), thuật toán hoạt động như thế này (mỗi lần hợp nhất hai khối lân cận):
+-------------------------------+
| 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)
+-------------------------------+
Đây là một trong những câu hỏi giúp tìm hiểu kiến trúc vi mô của bạn. Tôi chỉ định thời gian cho hai biến thể trong gcc 4.3.3 được biên dịch với -O3 bằng cách sử dụng nội tuyến C ++ để loại bỏ phí gọi hàm, một tỷ lần lặp, giữ tổng số chạy để đảm bảo trình biên dịch không xóa bất cứ điều gì quan trọng, sử dụng ndtsc cho thời gian ( chu kỳ đồng hồ chính xác).
nội tuyến int pop2 (không dấu x, không dấu 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); trả lại (x + y) & 0x000000FF; }
Delight chưa được sửa đổi của Hacker mất 12,2 gigacyc. Phiên bản song song của tôi (đếm gấp đôi số bit) chạy trong 13.0 gigacyc. Tổng số 10,5 giây trôi qua cho cả hai cùng nhau trên Core Duo 2,4 GHz. 25 gigaciking = chỉ hơn 10 giây ở tần số đồng hồ này, vì vậy tôi tự tin rằng thời gian của mình là đúng.
Điều này có liên quan đến các chuỗi phụ thuộc lệnh, rất tệ cho thuật toán này. Tôi có thể tăng gần gấp đôi tốc độ một lần nữa bằng cách sử dụng một cặp thanh ghi 64 bit. Trên thực tế, nếu tôi khéo léo và thêm x + ya sớm hơn tôi có thể tắt một số ca. Phiên bản 64 bit với một số điều chỉnh nhỏ sẽ xuất hiện đồng đều, nhưng lại tính gấp đôi số bit.
Với các thanh ghi SIMD 128 bit, một yếu tố khác của hai và các bộ hướng dẫn SSE cũng thường có các phím tắt thông minh.
Không có lý do gì để mã đặc biệt minh bạch. Giao diện rất đơn giản, thuật toán có thể được tham chiếu trực tuyến ở nhiều nơi và có thể kiểm tra đơn vị toàn diện. Các lập trình viên tình cờ tìm thấy nó thậm chí có thể học được điều gì đó. Các hoạt động bit là cực kỳ tự nhiên ở cấp độ máy.
OK, tôi quyết định chuẩn bị phiên bản 64-bit được điều chỉnh. Đối với một sizeof này (dài không dấu) == 8
nội tuyến int pop2 (dài không dấu x, dài không dấu y) { x = x - ((x >> 1) & 0x5555555555555555); y = y - ((y >> 1) & 0x5555555555555555); x = (x & 0x3333333333333333) + ((x >> 2) & 0x333333333333333333); y = (y & 0x3333333333333333) + ((y >> 2) & 0x333333333333333333); 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); trả lại x & 0xFF; }
Điều đó có vẻ đúng (mặc dù tôi không kiểm tra cẩn thận). Bây giờ thời gian xuất hiện ở mức 10,70 gigacyc / 14,1 gigacyc. Con số sau đó đã tóm tắt 128 tỷ bit và tương ứng với 5,9 giây trôi qua trên máy này. Phiên bản không song song tăng tốc một chút vì tôi đang chạy ở chế độ 64 bit và nó thích các thanh ghi 64 bit tốt hơn một chút so với các thanh ghi 32 bit.
Chúng ta hãy xem liệu có thêm một chút đường ống nào ở đây không. Điều này có liên quan nhiều hơn một chút, vì vậy tôi thực sự đã thử nghiệm một chút. Mỗi thuật ngữ một mình tính đến 64, tất cả tổng cộng thành 256.
int int4 (không dấu dài x, dài không dấu y, unsign dài u, unsign dài v) { enum {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); trả lại x & 0x000001FF; }
Tôi đã rất phấn khích một lúc, nhưng hóa ra gcc đang chơi các thủ thuật nội tuyến với -O3 mặc dù tôi không sử dụng từ khóa nội tuyến trong một số bài kiểm tra. Khi tôi để gcc chơi các thủ thuật, một tỷ cuộc gọi đến pop4 () mất 12,56 gigacyc, nhưng tôi xác định đó là các đối số gấp như các biểu thức không đổi. Một con số thực tế hơn dường như là 19,6gc để tăng thêm 30%. Vòng lặp thử nghiệm của tôi bây giờ trông như thế này, đảm bảo mỗi đối số đủ khác nhau để ngăn gcc chơi các thủ thuật.
hitime b4 = rdtsc (); cho (dài không dấu i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) tổng + = pop4 (i, i ^ 1, ~ i, i | 1); hitime e4 = rdtsc ();
256 tỷ bit được tóm tắt trong 8,17 giây. Hoạt động tới 1,02 giây cho 32 triệu bit như được điểm chuẩn trong tra cứu bảng 16 bit. Không thể so sánh trực tiếp, bởi vì băng ghế khác không cho tốc độ xung nhịp, nhưng có vẻ như tôi đã tát cái snot ra khỏi phiên bản bảng 64KB, đây là cách sử dụng bi thảm của bộ đệm L1 ở nơi đầu tiên.
Cập nhật: quyết định làm rõ ràng và tạo pop6 () bằng cách thêm bốn dòng trùng lặp. Đã đạt tới 22,8gc, 384 tỷ bit được tóm tắt trong 9,5 giây. Vì vậy, có 20% khác bây giờ ở 800ms cho 32 tỷ bit.
Tại sao không lặp lại chia cho 2?
đếm = 0 trong khi n> 0 if (n% 2) == 1 đếm + = 1 n / = 2
Tôi đồng ý rằng đây không phải là nhanh nhất, nhưng "tốt nhất" là hơi mơ hồ. Tôi muốn tranh luận rằng "tốt nhất" nên có yếu tố rõ ràng
Việc thay đổi bit Delight của Hacker trở nên rõ ràng hơn rất nhiều khi bạn viết ra các mẫu bit.
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;
}
Bước đầu tiên thêm các bit chẵn vào các bit lẻ, tạo ra một tổng số bit trong mỗi hai. Các bước khác thêm các khối thứ tự cao vào các khối thứ tự thấp, tăng gấp đôi kích thước khối cho đến khi chúng ta có số đếm cuối cùng chiếm toàn bộ int.
Đối với phương tiện hạnh phúc giữa bảng tra cứu 2 32 và lặp lại qua từng bit riêng lẻ:
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;
}
Điều này có thể được thực hiện trong đó O(k)
, k
số lượng bit được đặt.
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
n &= (n-1)
hình thức cô đọng hơn .
Đó không phải là giải pháp nhanh nhất hay tốt nhất, nhưng tôi đã tìm thấy câu hỏi tương tự theo cách của mình, và tôi bắt đầu suy nghĩ và suy nghĩ. cuối cùng tôi nhận ra rằng nó có thể được thực hiện như thế này nếu bạn gặp vấn đề từ phía toán học và vẽ biểu đồ, sau đó bạn thấy rằng đó là một hàm có một số phần định kỳ, và sau đó bạn nhận ra sự khác biệt giữa các thời kỳ ... vì vậy bạn đi đây
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))()
Hàm bạn đang tìm kiếm thường được gọi là "tổng số ngang" hoặc "tổng số dân" của một số nhị phân. Knuth thảo luận về nó trong Tiền phát hành 1A, tr11-12 (mặc dù có một tài liệu tham khảo ngắn trong Tập 2, 4.6.3- (7).)
Các classicus locus là bài viết Peter Wegner của "Một kỹ thuật cho Ones đếm trong một máy tính nhị phân", từ truyền thông của ACM , Tập 3 (1960) Số 5, trang 322 . Ông đưa ra hai thuật toán khác nhau ở đó, một thuật toán được tối ưu hóa cho các số dự kiến là "thưa thớt" (nghĩa là có một số lượng nhỏ) và một thuật toán cho trường hợp ngược lại.
Vài câu hỏi mở: -
chúng ta có thể sửa đổi thuật toán để hỗ trợ số âm như sau: -
count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
count += 1
n /= 2
return count
Bây giờ để khắc phục vấn đề thứ hai, chúng ta có thể viết thuật toán như: -
int bit_count(int num)
{
int count=0;
while(num)
{
num=(num)&(num-1);
count++;
}
return count;
}
để tham khảo đầy đủ xem:
Tôi nghĩ rằng phương pháp của Brian Kernighan cũng sẽ hữu ích ... Nó trải qua nhiều lần lặp như có các bit được đặt. Vì vậy, nếu chúng ta có một từ 32 bit chỉ với tập bit cao, thì nó sẽ chỉ đi một lần qua vòng lặp.
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;
}
Xuất bản năm 1988, Ngôn ngữ lập trình C 2nd Ed. (của Brian W. Kernighan và Dennis M. Ritchie) đề cập đến điều này trong bài tập 2-9. Vào ngày 19 tháng 4 năm 2006, Don Knuth đã chỉ ra cho tôi rằng phương pháp này "được Peter Wegner xuất bản lần đầu tiên trong CACM 3 (1960), 322. (Cũng được Derrick Lehmer phát hiện độc lập và xuất bản năm 1964 trong một cuốn sách do Beckenbach biên tập.)"
Tôi sử dụng mã dưới đây là trực quan hơn.
int countSetBits(int n) {
return !n ? 0 : 1 + countSetBits(n & (n-1));
}
Logic: n & (n-1) đặt lại bit thiết lập cuối cùng của n.
PS: Tôi biết đây không phải là giải pháp O (1), mặc dù là một giải pháp thú vị.
O(ONE-BITS)
. Nó thực sự là O (1) vì có nhiều nhất là 32 bit.
Bạn có ý nghĩa gì với "Thuật toán tốt nhất"? Mã rút ngắn hay mã nhịn? Mã của bạn trông rất thanh lịch và nó có thời gian thực hiện liên tục. Mã này cũng rất ngắn.
Nhưng nếu tốc độ là yếu tố chính chứ không phải kích thước mã thì tôi nghĩ việc theo dõi có thể nhanh hơn:
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 );
}
Tôi nghĩ rằng điều này sẽ không nhanh hơn đối với giá trị 64 bit nhưng giá trị 32 bit có thể nhanh hơn.
Tôi đã viết một macro bitcount nhanh cho các máy RISC vào khoảng năm 1990. Nó không sử dụng số học nâng cao (nhân, chia,%), tìm nạp bộ nhớ (cách quá chậm), các nhánh (cách quá chậm), nhưng nó cho rằng CPU có một Bộ dịch chuyển thùng 32 bit (nói cách khác, >> 1 và >> 32 có cùng số lượng chu kỳ.) Giả sử rằng các hằng số nhỏ (như 6, 12, 24) không tốn gì để tải vào các thanh ghi hoặc được lưu trữ trong thời gian và tái sử dụng nhiều lần.
Với các giả định này, nó đếm 32 bit trong khoảng 16 chu kỳ / hướng dẫn trên hầu hết các máy RISC. Lưu ý rằng 15 hướng dẫn / chu trình gần với giới hạn dưới của số chu kỳ hoặc hướng dẫn, bởi vì dường như phải mất ít nhất 3 hướng dẫn (mặt nạ, ca, toán tử) để cắt giảm một nửa số phụ lục, vì vậy log_2 (32) = 5, 5 x 3 = 15 hướng dẫn là một mệnh lệnh thấp hơn.
#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;
Đây là một bí mật cho bước đầu tiên và phức tạp nhất:
input output
AB CD Note
00 00 = AB
01 01 = AB
10 01 = AB - (A >> 1) & 0x1
11 10 = AB - (A >> 1) & 0x1
vì vậy nếu tôi lấy cột thứ 1 (A) ở trên, dịch chuyển sang phải 1 bit và trừ nó khỏi AB, tôi nhận được đầu ra (CD). Phần mở rộng thành 3 bit là tương tự; bạn có thể kiểm tra nó với một bảng boolean 8 hàng như của tôi ở trên nếu bạn muốn.
nếu bạn đang sử dụng C ++, một tùy chọn khác là sử dụng siêu lập trình mẫu:
// 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;
}
sử dụng sẽ là:
// 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 )
tất nhiên bạn có thể mở rộng thêm mẫu này để sử dụng các loại khác nhau (thậm chí tự động phát hiện kích thước bit) nhưng tôi đã giữ nó đơn giản cho rõ ràng.
chỉnh sửa: quên đề cập đến điều này là tốt vì nó sẽ hoạt động trong bất kỳ trình biên dịch C ++ nào và về cơ bản nó chỉ hủy bỏ vòng lặp của bạn nếu giá trị không đổi được sử dụng cho số bit (nói cách khác, tôi khá chắc chắn đó là phương pháp chung nhanh nhất bạn sẽ tìm thấy)
constexpr
mặc dù.
Tôi đặc biệt thích ví dụ này từ tệp tài sản:
#define BITCOUNT (x) (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 255) #define BX_ (x) ((x) - (((x) >> 1) & 0x77777777) - (((x) >> 2) & 0x33333333) - (((x) >> 3) & 0x11111111))
Tôi thích nó nhất vì nó rất đẹp!
Java JDK1.5
Số nguyên.bitCount (n);
Trong đó n là số có 1 số được tính.
cũng kiểm tra
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);
}
Tôi đã tìm thấy một triển khai đếm bit trong một mảng bằng cách sử dụng lệnh SIMD (SSSE3 và AVX2). Nó có hiệu suất tốt hơn gấp 2-2,5 lần so với việc nó sẽ sử dụng hàm nội tại __popcnt64.
Phiên bản 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];
}
Phiên bản 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];
}
Có nhiều thuật toán để đếm các bit thiết lập; nhưng tôi nghĩ cái tốt nhất là cái nhanh hơn Bạn có thể xem chi tiết trên trang này:
Tôi đề nghị cái này:
Đếm các bit được đặt trong các từ 14, 24 hoặc 32 bit bằng hướng dẫn 64 bit
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;
Phương pháp này đòi hỏi CPU 64 bit có phân chia mô đun nhanh để có hiệu quả. Tùy chọn đầu tiên chỉ mất 3 thao tác; tùy chọn thứ hai mất 10; và tùy chọn thứ ba mất 15.
Giải pháp C # nhanh bằng cách sử dụng bảng tính toán trước của bit Byte với phân nhánh trên kích thước đầu vào.
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
mà không cần truy cập bộ nhớ ...
Đây là một mô-đun di động (ANSI-C) có thể điểm chuẩn từng thuật toán của bạn trên bất kỳ kiến trúc nào.
CPU của bạn có 9 bit byte? Không có vấn đề gì :-) Tại thời điểm này, nó thực hiện 2 thuật toán, thuật toán K & R và bảng tra cứu thông minh byte. Bảng tra cứu trung bình nhanh hơn 3 lần so với thuật toán K & R. Nếu ai đó có thể tìm ra cách để làm cho thuật toán "Hacker's Delight" có thể di chuyển, hãy thêm nó vào.
#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
những gì bạn có thể làm là
while(n){
n=n&(n-1);
count++;
}
logic đằng sau này là các bit của n-1 được đảo ngược từ bit được đặt ngoài cùng bên phải của n. nếu n = 6 tức là 110 thì 5 là 101, các bit được đảo ngược từ bit đặt bên phải của n. Vì vậy, nếu chúng ta và hai cái này, chúng ta sẽ tạo ra bit 0 đúng nhất trong mỗi lần lặp và luôn đi đến bit được đặt ở bên phải tiếp theo. Tính, đếm bit được đặt. Độ phức tạp thời gian tồi tệ nhất sẽ là O (logn) khi mỗi bit được đặt.