จะนับจำนวนบิตที่ตั้งค่าเป็นจำนวนเต็ม 32 บิตได้อย่างไร?


868

8 บิตที่เป็นตัวแทนของหมายเลข 7 มีลักษณะดังนี้:

00000111

ตั้งสามบิต

อัลกอริทึมในการกำหนดจำนวนบิตที่ตั้งไว้ในจำนวนเต็ม 32- บิตคืออะไร?


101
นี่คือน้ำหนักของ Hamming BTW
Purfideas

11
แอปพลิเคชันสำหรับโลกแห่งนี้คืออะไร? (สิ่งนี้ไม่ได้ถูกนำมาเป็นคำวิจารณ์ - ฉันแค่อยากรู้อยากเห็น.)
jonmorgan

8
การคำนวณพาริตี้บิต (ค้นหา) ซึ่งใช้เป็นการตรวจหาข้อผิดพลาดอย่างง่ายในการสื่อสาร
Dialecticus

8
@Dialecticus การคำนวณพาริตี้บิตมีราคาถูกกว่าการคำนวณน้ำหนัก Hamming
finnw

15
@spookyjon สมมติว่าคุณมีกราฟแสดงเป็นเมทริกซ์ adjacency ซึ่งเป็นชุดบิต หากคุณต้องการคำนวณจำนวนขอบของจุดสุดยอดมันจะลดลงเพื่อคำนวณน้ำหนัก Hamming ของหนึ่งแถวในชุดบิต
fuz

คำตอบ:


850

สิ่งนี้เรียกว่า ' Hamming Weight ', 'popcount' หรือ 'การเพิ่มด้านข้าง'

อัลกอริทึม 'ดีที่สุด' ขึ้นอยู่กับ CPU ของคุณและรูปแบบการใช้งานของคุณ

ซีพียูบางตัวมีคำสั่งในตัวเดียวที่ต้องทำและบางตัวก็มีคำสั่งแบบขนานซึ่งทำหน้าที่เป็นเวกเตอร์บิต คำแนะนำแบบขนาน (เช่น x86 popcntบนซีพียูที่รองรับ) เกือบจะเร็วที่สุดแน่นอน สถาปัตยกรรมอื่น ๆ บางแห่งอาจมีคำสั่งที่ช้าซึ่งนำมาใช้กับลูป microcoded ที่ทดสอบบิตต่อรอบ ( อ้างอิงที่จำเป็น )

วิธีการค้นหาตารางที่เติมไว้ล่วงหน้าสามารถทำได้อย่างรวดเร็วหาก CPU ของคุณมีแคชขนาดใหญ่และ / หรือคุณกำลังทำตามคำแนะนำเหล่านี้จำนวนมากในการวนรอบที่แน่น อย่างไรก็ตามอาจมีปัญหาเนื่องจากค่าใช้จ่ายของ 'cache miss' ซึ่ง CPU ต้องดึงข้อมูลบางส่วนจากหน่วยความจำหลัก (ค้นหาแต่ละไบต์แยกกันเพื่อทำให้โต๊ะเล็ก)

หากคุณรู้ว่าไบต์ของคุณจะเป็นส่วนใหญ่ 0 หรือส่วนใหญ่ 1 แล้วมีอัลกอริทึมที่มีประสิทธิภาพมากสำหรับสถานการณ์เหล่านี้

ฉันเชื่อว่าอัลกอริทึมสำหรับวัตถุประสงค์ทั่วไปที่ดีมากมีดังต่อไปนี้เรียกว่า 'ขนาน' หรือ 'อัลกอริทึม SWAR ที่มีความแม่นยำของตัวแปร' ฉันได้แสดงสิ่งนี้ในภาษาหลอกซีเช่นคุณอาจต้องปรับให้ทำงานสำหรับภาษาเฉพาะ (เช่นใช้ uint32_t สำหรับ C ++ และ >>> ใน 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 Bithack นี้:

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

ขั้นตอนแรกคือเวอร์ชั่นที่เหมาะที่สุดของการปิดบังเพื่อแยกบิตคี่ / คู่ให้เลื่อนไปตามแถวและเพิ่ม สิ่งนี้จะทำการแยก 16 ส่วนเพิ่มเติมในตัวสะสมแบบ 2 บิต ( SWAR = SIMD ภายในการลงทะเบียน ) กด(i & 0x55555555) + ((i>>1) & 0x55555555)ไลค์

ขั้นตอนถัดไปใช้เวลาคี่ / คู่แปดเท่าของตัวสะสม 16x2 บิตเหล่านั้นและเพิ่มอีกครั้งโดยสร้างผลรวม 8x 4 บิต การi - ...เพิ่มประสิทธิภาพไม่สามารถทำได้ในครั้งนี้ดังนั้นจึงเป็นเพียงหน้ากากก่อน / หลังการเปลี่ยน ใช้0x33...ค่าคงที่เดียวกันทั้งสองครั้งแทน0xccc...ก่อนการเปลี่ยนเป็นสิ่งที่ดีเมื่อรวบรวม ISAs ที่จำเป็นต้องสร้างค่าคงที่แบบ 32 บิตในการลงทะเบียนแยกต่างหาก

ขั้นตอนสุดท้ายของการเลื่อนและเพิ่มความ(i + (i >> 4)) & 0x0F0F0F0Fกว้างเป็น 4x8 บิตสะสม มันมาสก์หลังจากเพิ่มแทนก่อนเพราะค่าสูงสุดในการสะสม 4 บิตใด ๆ คือ4ถ้าทั้ง 4 บิตของบิตที่สอดคล้องกันถูกตั้งค่า 4 + 4 = 8 ซึ่งยังคงเหมาะกับใน 4 i + (i >> 4)บิตเพื่อให้การดำเนินการระหว่างองค์ประกอบตอดเป็นไปไม่ได้ใน

จนถึงตอนนี้เป็นเพียงปกติ SIMD โดยใช้เทคนิค SWAR ด้วยการเพิ่มประสิทธิภาพที่ชาญฉลาด การดำเนินการต่อด้วยรูปแบบเดียวกันอีก 2 ขั้นตอนสามารถขยายเป็น 2x16- บิตแล้วนับ 1x 32- บิต แต่มีวิธีที่มีประสิทธิภาพมากขึ้นในเครื่องที่มีฮาร์ดแวร์ที่รวดเร็วคูณ:

เมื่อเรามีไม่กี่พอ "องค์ประกอบ" คูณด้วยค่าคงมายากลสามารถรวมองค์ประกอบทั้งหมดลงในองค์ประกอบด้านบน ในกรณีนี้องค์ประกอบไบต์ คูณจะกระทำโดยซ้ายขยับและการเพิ่มดังนั้นคูณของผลในการx * 0x01010101 x + (x<<8) + (x<<16) + (x<<24) อิลิเมนต์ 8 บิตของเรานั้นกว้างพอ (และถือจำนวนที่น้อยพอ) ซึ่งสิ่งนี้ไม่ได้ส่งไปยัง 8 บิตสูงสุด

รุ่น 64 บิตนี้สามารถทำ 8x องค์ประกอบ 8 บิตในจำนวนเต็ม 64 บิตกับ 0x0101010101010101 >>56คูณและสารสกัดจากไบต์สูงด้วย ดังนั้นจึงไม่ทำตามขั้นตอนพิเศษใด ๆ เพียงแค่ค่าคงที่ที่กว้างขึ้น นี่คือสิ่งที่ GCC ใช้สำหรับ__builtin_popcountllระบบ x86 เมื่อpopcntไม่ได้เปิดใช้งานคำสั่งฮาร์ดแวร์ หากคุณสามารถใช้ builtins หรือ intrinsics สำหรับสิ่งนี้ให้เปิดโอกาสให้คอมไพเลอร์มีโอกาสทำการปรับแต่งเฉพาะเป้าหมาย


ด้วย SIMD แบบเต็มสำหรับเวกเตอร์ที่กว้างขึ้น (เช่นการนับอาร์เรย์ทั้งหมด)

อัลกอริธึม bitwise-SWAR นี้สามารถทำขนานกันได้ในองค์ประกอบเวกเตอร์หลายรายการพร้อมกันแทนการลงทะเบียนเลขจำนวนเต็มเดียวเพื่อเพิ่มความเร็วของ CPU ที่มี SIMD แต่ไม่มีคำสั่ง popcount ที่ใช้งานได้ (เช่นรหัส x86-64 ที่ต้องรันบน CPU ใด ๆ ไม่ใช่เฉพาะ Nehalem หรือใหม่กว่า)

อย่างไรก็ตามวิธีที่ดีที่สุดในการใช้คำแนะนำแบบเวกเตอร์สำหรับ popcount คือโดยใช้ตัวแปรแบบสุ่มเพื่อทำการค้นหาแบบตารางสำหรับ 4 บิตในแต่ละครั้งของแต่ละไบต์แบบขนาน (ดัชนี 4 บิตรายการตาราง 16 ที่จัดขึ้นในการลงทะเบียนแบบเวกเตอร์)

บน CPU ของ Intel, การเรียนการสอนฮาร์ดแวร์ 64bit popcnt สามารถ outperform SSSE3 PSHUFBบิตขนานการดำเนินงานโดยเกี่ยวกับปัจจัย 2 แต่ถ้าคอมไพเลอร์ของคุณได้รับมันเพียงขวา มิฉะนั้น SSE จะออกมาข้างหน้าอย่างมีนัยสำคัญ เวอร์ชั่นคอมไพเลอร์รุ่นใหม่จะรับรู้ถึงปัญหาการพึ่งพา popcnt false ใน Intelปัญหาเกี่ยวกับอินเทล

อ้างอิง:


87
ฮ่า! ชอบฟังก์ชั่น NumberOfSetBits () แต่ขอให้โชคดีในการตรวจสอบรหัส :-)
Jason S

37
บางทีมันควรจะใช้unsigned intเพื่อแสดงให้เห็นว่าไม่มีสัญญาณแทรกซ้อนใด ๆ จะuint32_tปลอดภัยกว่าเช่นเดียวกับที่คุณได้รับสิ่งที่คุณคาดหวังในทุกแพลตฟอร์ม?
Craig McQueen

35
@nonnb: ที่จริงแล้วตามที่เขียนไว้รหัสนั้นมีความผิดพลาดและต้องการการบำรุงรักษา >>มีการกำหนดการใช้งานสำหรับค่าลบ ความต้องการอาร์กิวเมนต์จะมีการเปลี่ยนแปลง (หรือหล่อ) ไปunsignedและตั้งแต่รหัส 32 uint32_tบิตเฉพาะก็อาจจะใช้
. GitHub หยุดช่วยน้ำแข็ง

6
มันไม่ได้วิเศษจริงๆ มันเพิ่มชุดของบิต แต่การทำเช่นนั้นกับการเพิ่มประสิทธิภาพที่ฉลาดบางอย่าง ลิงก์วิกิพีเดียที่ให้ไว้ในคำตอบนั้นเป็นงานที่ดีในการอธิบายสิ่งที่เกิดขึ้น แต่ฉันจะไปทีละบรรทัด 1) นับจำนวนบิตในทุกๆคู่ของบิตวางจำนวนนั้นลงในคู่ของบิตนั้น (คุณจะมี 00, 01 หรือ 10) บิต "ฉลาด" ที่นี่คือการลบที่หลีกเลี่ยงหนึ่งหน้ากาก 2) เพิ่มคู่ของจำนวนเงินเหล่านั้นของ bitpairs ลงใน nibbles ที่สอดคล้องกันของพวกเขา; ไม่มีอะไรที่ฉลาดที่นี่ แต่แต่ละแทะจะมีค่า 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 ให้ทำงานเร็วขึ้น

Intrinsics ของ GCC สามารถใช้งานได้กับหลายแพลตฟอร์ม Popcount จะกลายเป็นกระแสหลักในสถาปัตยกรรม x86 ดังนั้นจึงเป็นเรื่องเหมาะสมที่จะเริ่มใช้อินทรินในตอนนี้ สถาปัตยกรรมอื่น ๆ มี popcount มานานหลายปี


บน x86 คุณสามารถบอกคอมไพเลอร์ว่าสามารถรองรับการpopcntเรียนการสอนด้วย-mpopcntหรือ-msse4.2เพื่อเปิดใช้งานคำแนะนำเวกเตอร์ที่เพิ่มเข้ามาในรุ่นเดียวกัน ดูตัวเลือก x86 GCC -march=nehalem(หรือ-march=ซีพียูอะไรก็ตามที่คุณต้องการให้โค้ดของคุณสมมติและปรับแต่ง) อาจเป็นตัวเลือกที่ดี การเรียกใช้ไบนารีที่เกิดขึ้นบน CPU ตัวเก่าจะส่งผลให้เกิดความผิดปกติในการสอน

ในการทำให้ไบนารีปรับให้เหมาะสมสำหรับเครื่องที่คุณสร้างให้ใช้-march=native (ด้วย gcc, clang หรือ ICC)

MSVC ให้popcntคำแนะนำที่แท้จริงสำหรับคำสั่งx86แต่ต่างจาก gcc ซึ่งเป็นคำแนะนำที่แท้จริงสำหรับคำสั่งฮาร์ดแวร์และต้องการการสนับสนุนฮาร์ดแวร์


ใช้std::bitset<>::count()แทนการติดตั้งในตัว

ในทางทฤษฎีคอมไพเลอร์ใด ๆ ที่รู้วิธีการ popcount ได้อย่างมีประสิทธิภาพสำหรับ CPU เป้าหมายควรเปิดเผยการทำงานที่ผ่านการรับรองมาตรฐาน ISO C std::bitset<>++ ในทางปฏิบัติคุณอาจจะดีกว่าด้วยบิตแฮ็คและ / shift / ADD ในบางกรณีสำหรับซีพียูเป้าหมายบางตัว

สำหรับสถาปัตยกรรมเป้าหมายที่ป็อปอัพฮาร์ดแวร์เป็นส่วนเสริมเพิ่มเติม (เช่น x86) คอมไพเลอร์บางตัวstd::bitsetจะมีข้อดีที่ใช้เมื่อมี ตัวอย่างเช่น MSVC ไม่มีวิธีที่จะเปิดใช้งานpopcntการสนับสนุนในเวลารวบรวมและมักจะใช้การค้นหาตารางแม้จะมี/Ox /arch:AVX(ซึ่งหมายถึง SSE4.2 แม้ว่าในทางเทคนิคจะมีบิตคุณลักษณะแยกต่างหากสำหรับpopcnt)

แต่อย่างน้อยคุณก็มีอุปกรณ์พกพาที่ใช้งานได้ทุกที่และด้วย gcc / clang ด้วยตัวเลือกเป้าหมายที่ถูกต้องคุณจะได้รับ popcount ฮาร์ดแวร์สำหรับสถาปัตยกรรมที่รองรับ

#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();
}

ดูasm จาก gcc, clang, icc และ MSVCบนตัวรวบรวมคอมไพเลอร์ Godbolt

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 เฉพาะเลย แต่จะรวบรวมได้ดีสำหรับ x86 ด้วย gcc / clang / icc

นอกจากนี้โปรดทราบว่าการย้อนกลับของ gcc สำหรับสถาปัตยกรรมที่ไม่มี popcount คำสั่งเดียวคือการค้นหาตารางแบบไบต์ต่อครั้ง มันไม่ได้ยอดเยี่ยมสำหรับ ARM เช่นกัน


5
ฉันยอมรับว่านี่เป็นวิธีปฏิบัติที่ดีโดยทั่วไป แต่สำหรับ XCode / OSX / Intel ฉันพบว่ามันจะสร้างรหัสช้ากว่าคำแนะนำส่วนใหญ่ที่โพสต์ที่นี่ ดูคำตอบของฉันสำหรับรายละเอียด

5
Intel i5 / i7 มีคำสั่ง SSE4 POPCNT ซึ่งทำได้โดยใช้การลงทะเบียนวัตถุประสงค์ทั่วไป GCC ในระบบของฉันไม่ได้ปล่อยคำสั่งนั้นโดยใช้สิ่งที่แท้จริงนี้ฉันเดาเพราะยังไม่มีตัวเลือก -march = nehalem
matja

3
@matja, GCC 4.4.1 ของฉันส่งเสียงคำสั่ง popcnt ถ้าฉันคอมไพล์ด้วย -msse4.2
Nils Pipenbrinck

74
ใช้ C ++ std::bitset::count's หลังจากอินไลน์การคอมไพล์นี้เป็นการ__builtin_popcountโทรครั้งเดียว
deft_code

1
@nlucaroni ใช่ เวลากำลังเปลี่ยนแปลง ฉันได้เขียนคำตอบนี้ในปี 2008 ทุกวันนี้เรามี popcount ดั้งเดิมและผู้ใช้ที่แท้จริงจะรวบรวมคำสั่งแอสเซมเบลอร์เดียวหากแพลตฟอร์มอนุญาต
Nils Pipenbrinck

184

ในความคิดของฉันทางออกที่ดีที่สุดคือโซลูชันที่สามารถอ่านได้โดยโปรแกรมเมอร์คนอื่น (หรือโปรแกรมเมอร์ดั้งเดิมในอีกสองปีต่อมา) โดยไม่มีความคิดเห็นมากมาย คุณอาจต้องการโซลูชันที่เร็วที่สุดหรือฉลาดที่สุดซึ่งบางอันมีให้อยู่แล้ว แต่ฉันชอบความสามารถในการอ่านมากกว่าความฉลาดในเวลาใดก็ได้

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 ... " คุณควรใช้โอเปอเรเตอร์กะ (>>) แล้วออกความคิดเห็น
indiv

9
มันจะไม่มีเหตุผลที่จะแทนที่if ((value & 1) == 1) { count++; }ด้วยcount += value & 1เหรอ?
Ponkadoodle

21
ไม่ทางออกที่ดีที่สุดไม่ใช่กรณีที่อ่านได้มากที่สุดในกรณีนี้ อัลกอริทึมที่ดีที่สุดนี่คือวิธีที่เร็วที่สุด
NikiC

21
นั่นเป็นความคิดเห็นของคุณอย่างสิ้นเชิง @nikic แม้ว่าคุณจะมีอิสระที่จะลงคะแนนให้ฉัน ไม่มีการพูดถึงในคำถามเกี่ยวกับวิธีการหาปริมาณ "ดีที่สุด" คำว่า "ประสิทธิภาพ" หรือ "เร็ว" สามารถมองเห็นได้ที่ไหนเลย นั่นเป็นเหตุผลที่ฉันเลือกอ่านได้
paxdiablo

3
ฉันอ่านคำตอบนี้ 3 ปีต่อมาและฉันคิดว่ามันเป็นคำตอบที่ดีที่สุดเพราะสามารถอ่านได้และมีความคิดเห็นเพิ่มเติม ระยะเวลา
waka-waka-waka

98

จากความยินดีของแฮกเกอร์หน้า 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
วิธีการจาวาInteger.bitCount(int)ใช้การปฏิบัติที่แน่นอนแบบเดียวกันนี้
Marco Bolis

มีปัญหาเล็กน้อยในการติดตามสิ่งนี้ - มันจะเปลี่ยนไปอย่างไรถ้าเราใส่ใจเฉพาะค่าประมาณ 16 บิตแทนที่จะเป็น 32- บิต?
Jeremy Blum

บางทีแฮ็กเกอร์อาจปีติยินดี แต่ฉันอยากให้ทุกคนเรียกสิ่งนี้popแทนpopulation_count(หรือpop_cntถ้าคุณต้องมีการยกเลิก) @MarcoBolis ผมเข้าใจว่าจะเป็นจริงของทุกรุ่นของ Java แต่อย่างเป็นทางการว่าจะมีการดำเนินการขึ้นอยู่ :)
มาร์ติน Bodewes

และสิ่งนี้ไม่จำเป็นต้องมีการคูณเช่นรหัสในคำตอบที่ได้รับการยอมรับ
อเล็กซ์

โปรดทราบว่าในการสรุปเป็น 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); 

จำนวนบิตในสองบิตสามารถ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ผลิตอื่นมันผลิต0b010b00

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

ข้อความนี้ควรเข้าใจง่าย หลังจากการดำเนินการครั้งแรกเรามีจำนวนบิตที่ตั้งค่าในทุก ๆ สองบิตตอนนี้เราจะสรุปจำนวนนั้นในทุกๆ 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 แทน เรารู้ว่า - เนื่องจากการดำเนินการก่อนหน้าของเรา - ที่ทุก ๆ nibble มีจำนวนบิตที่ตั้งไว้ ลองดูตัวอย่าง 0b01000010สมมติว่าเรามีไบต์ มันหมายความว่าตอดแรกมีชุด 4 บิตและอันที่สองมีชุด 2 บิต ตอนนี้เราเพิ่ม nibbles เหล่านั้นเข้าด้วยกัน

0b01000010 + 0b01000000

มันทำให้เรานับจำนวนบิตที่ตั้งไว้ในไบต์ในแทะแรก0b01100010และดังนั้นเราจึงปกปิดสี่ไบต์สุดท้ายของไบต์ทั้งหมดในจำนวน (ละทิ้ง)

0b01100010 & 0xF0 = 0b01100000

ตอนนี้ทุกไบต์มีจำนวนบิตที่ตั้งไว้ เราต้องรวมทั้งหมดเข้าด้วยกัน เคล็ดลับคือการคูณผลลัพธ์ด้วย0b10101010คุณสมบัติที่น่าสนใจ ถ้าจำนวนของเรามีสี่ไบต์มันจะส่งผลให้ในจำนวนใหม่ที่มีไบต์เหล่านี้A B C D A+B+C+D B+C+D C+D Dหมายเลข 4 ไบต์สามารถตั้งค่าได้สูงสุด 32 บิตซึ่งสามารถแทน0b00100000ได้

>> 24ทั้งหมดที่เราต้องการตอนนี้คือไบต์แรกซึ่งมีผลรวมของทุกบิตชุดไบต์ทั้งหมดและเราได้รับมันโดย อัลกอริทึมนี้ถูกออกแบบมาสำหรับ32 bitคำ แต่สามารถแก้ไขได้ง่ายสำหรับ64 bitคำ


อะไรคือสิ่งที่c = เกี่ยวกับ? ดูเหมือนว่าควรจะถูกกำจัด เพิ่มเติมแนะนำชุดค่า Paren พิเศษ A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" เพื่อหลีกเลี่ยงคำเตือนแบบคลาสสิก
chux - Reinstate Monica

4
คุณลักษณะที่สำคัญคือที่นี้ 32 บิตประจำทำงานให้กับทั้งสองและpopcount(int v) popcount(unsigned v)เพื่อความสะดวกในการพกพาลองพิจารณาpopcount(uint32_t v)เช่นชิ้นส่วน * 0x1010101
chux - Reinstate Monica

ซอส ? (หนังสือลิงค์ชื่อผู้ทำคำแปล ฯลฯ ) ยินดีอย่างมาก เพราะจากนั้นเราสามารถวางลงในฐานโค้ดของเราพร้อมกับความคิดเห็นว่ามาจากไหน
v.oddou

1
ฉันคิดว่าเพื่อความชัดเจนที่ดีกว่าบรรทัดสุดท้ายควรเขียนเป็น: return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;ดังนั้นเราจึงไม่จำเป็นต้องนับตัวอักษรเพื่อดูสิ่งที่คุณกำลังทำจริง ๆ (เนื่องจากคุณละทิ้งคนแรก0ฉันตั้งใจคิดว่าคุณใช้รูปแบบบิตผิด (พลิก) เป็นหน้ากาก - นั่นคือจนกว่าฉันจะสังเกตเห็นว่ามีเพียง 7 ตัวอักษรและไม่ได้ 8)
emem

การคูณด้วย 0x01010101 นั้นอาจช้าขึ้นอยู่กับโปรเซสเซอร์ ตัวอย่างเช่นใน PowerBook G4 รุ่นเก่าของฉันการคูณ 1 ครั้งนั้นช้ากว่าการเพิ่มอีก 4 ครั้ง (ไม่แย่เท่าการหารโดยที่ 1 การแบ่งนั้นช้ากว่าการเพิ่ม 23 ครั้ง)
George Koehler

54

ฉันเบื่อและตั้งเวลาซ้ำสามพันล้านครั้งในสามวิธี คอมไพเลอร์คือ gcc -O3 CPU คือสิ่งที่พวกเขาใส่ใน Macbook Pro รุ่นที่ 1

เร็วที่สุดคือต่อไปนี้ที่ 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] );
}

อันดับที่สองไปที่รหัสเดียวกัน แต่ค้นหา 4 ไบต์แทน 2 halfwords ใช้เวลาประมาณ 5.5 วินาที

อันดับที่สามใช้แนวทาง 'การเพิ่มด้านข้าง' ซึ่งใช้เวลา 8.6 วินาที

อันดับที่สี่ไปที่ __builtin_popcount () ของ GCC ในเวลา 11 วินาทีที่น่าอับอาย

การนับหนึ่งครั้งต่อครั้งช้ากว่าและฉันเบื่อที่จะรอให้เสร็จ

ดังนั้นหากคุณใส่ใจกับประสิทธิภาพเหนือสิ่งอื่นใดให้ใช้วิธีแรก หากคุณสนใจ แต่ไม่เพียงพอที่จะใช้ RAM ขนาด 64Kb ให้ใช้วิธีที่สอง มิฉะนั้นให้ใช้วิธีอ่านแบบทีละครั้ง (แต่ช้า)

มันยากที่จะคิดว่าสถานการณ์ที่คุณต้องการใช้วิธีการทวิ - ทวิ

แก้ไข: ผลลัพธ์ที่คล้ายกันที่นี่


49
@ ไมค์วิธีการตามตารางไม่สามารถเอาชนะได้หากตารางอยู่ในแคช สิ่งนี้เกิดขึ้นในการวัดแบบไมโคร (เช่นทำการทดสอบหลายล้านครั้งในวงแคบ) อย่างไรก็ตามแคชมิสใช้เวลาประมาณ 200 รอบและแม้แต่ popcount ที่ไร้เดียงสาที่สุดก็จะเร็วขึ้นที่นี่ มันขึ้นอยู่กับแอปพลิเคชันเสมอ
Nils Pipenbrinck

10
หากคุณไม่ได้เรียกรูทีนนี้สักสองสามล้านครั้งในวงแคบ ๆ คุณก็ไม่มีเหตุผลที่จะสนใจประสิทธิภาพของมันเลยและอาจใช้วิธีการที่ไร้เดียงสา แต่อ่านได้เนื่องจากการสูญเสียประสิทธิภาพนั้นไม่มีความสำคัญ และ FWIW 8 บิต LUT ได้รับแคชร้อนภายใน 10-20 สาย

6
ฉันไม่คิดว่ามันเป็นเรื่องยากที่จะจินตนาการถึงสถานการณ์ที่นี่คือการโทรแบบลีฟที่ทำจากวิธี - ทำแอพของคุณอย่างหนัก ขึ้นอยู่กับสิ่งอื่นที่เกิดขึ้น (และเธรด) เวอร์ชันที่เล็กกว่าอาจชนะได้ มีการเขียนอัลกอริธึมจำนวนมากที่เอาชนะคู่แข่งได้เนื่องจากมีการอ้างอิงที่ดีกว่า ทำไมไม่ทำเช่นนี้ด้วยล่ะ
Jason

ลองนี้ด้วยเสียงดังกราวมันอย่างมีนัยสำคัญอย่างชาญฉลาดที่ builtins การดำเนินการ
Matt Joiner

3
GCC จะไม่ส่งคำสั่ง popcont เว้นแต่ว่าเรียกด้วย -msse4.2 ซึ่งเป็นกรณีที่เร็วกว่า 'การเพิ่มด้านข้าง'
lvella

54

หากคุณบังเอิญใช้จาวาวิธีการในตัวInteger.bitCountจะทำเช่นนั้น


เมื่อดวงอาทิตย์มี API ที่แตกต่างกันมันต้องใช้ตรรกะบางอย่างบนพื้นหลังใช่มั้ย
Vallabh Patade

2
ขณะที่ทราบด้านการดำเนินงานของ Java ใช้เดียวกันอัลกอริทึมแหลมออกโดยเควินเล็ก ๆ น้อย ๆ
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;
}

ให้ฉันอธิบายอัลกอริทึมนี้

อัลกอริทึมนี้ขึ้นอยู่กับอัลกอริทึมหารและพิชิต สมมติว่ามีจำนวนเต็ม 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

29

นี่เป็นหนึ่งในคำถามที่ช่วยให้รู้จักสถาปัตยกรรมไมโครของคุณ ฉันเพิ่งตั้งเวลาตัวแปรสองตัวภายใต้ gcc 4.3.3 ที่คอมไพล์ด้วย -O3 โดยใช้อินไลน์ C ++ เพื่อกำจัดโอเวอร์เฮดการเรียกใช้ฟังก์ชันหนึ่งพันล้านซ้ำการรักษาผลรวมการทำงานของจำนวนทั้งหมดเพื่อให้แน่ใจว่าคอมไพเลอร์ไม่ได้เอาสิ่งใดสำคัญออก รอบนาฬิกาแม่นยำ)

อินไลน์ 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;
}

Delight ของแฮ็กเกอร์ที่ไม่ได้แก้ไขนั้นใช้เวลา 12.2 กิกะบิต รุ่นคู่ขนานของฉัน (นับเป็นสองเท่าของจำนวนบิต) ทำงานใน 13.0 กิกะบิต 10.5 วินาทีที่ผ่านมารวมกันสำหรับทั้งคู่ใน 2.4GHz Core Duo 25 gigacycles = เพียง 10 วินาทีที่ความถี่สัญญาณนาฬิกาดังนั้นฉันมั่นใจว่าเวลาของฉันถูกต้องแล้ว

สิ่งนี้เกี่ยวข้องกับกลุ่มพึ่งพาการเรียนการสอนซึ่งไม่ดีสำหรับอัลกอริทึมนี้ ฉันสามารถเพิ่มความเร็วเป็นสองเท่าได้อีกครั้งด้วยการลงทะเบียน 64 บิต ในความเป็นจริงถ้าฉันฉลาดและเพิ่ม x + ya เร็ว ๆ นี้ฉันจะสามารถกำจัดการเปลี่ยนแปลงบางอย่างได้ รุ่น 64 บิตที่มีการปรับแต่งเล็กน้อยจะออกมาเท่า ๆ กัน แต่นับเป็นสองเท่าเป็นจำนวนมากอีกครั้ง

ด้วยการลงทะเบียน SIMD ขนาด 128 บิต แต่เป็นอีกปัจจัยที่สองและชุดคำสั่ง SSE มักจะมีทางลัดที่ฉลาดเช่นกัน

ไม่มีเหตุผลใดที่รหัสจะโปร่งใสโดยเฉพาะ อินเทอร์เฟซนั้นง่ายอัลกอริทึมสามารถอ้างอิงออนไลน์ในหลาย ๆ สถานที่และสามารถตอบสนองต่อการทดสอบหน่วยที่ครอบคลุม โปรแกรมเมอร์ที่สะดุดเมื่อมันอาจเรียนรู้บางสิ่งบางอย่าง การทำงานของบิตเหล่านี้เป็นเรื่องธรรมดามากในระดับเครื่อง

ตกลงฉันตัดสินใจที่จะพิพากษาเวอร์ชัน 64 บิตที่ได้รับการปรับแต่ง สำหรับขนาดนี้ (ยาวไม่ได้ลงนาม) == 8

inline int pop2 (ยาวไม่ลงนาม x, ไม่ได้ลงนามยาว y)
{
    x = x - ((x >> 1) & 0x5555555555555555);
    y = y - ((y >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    y = (y & 0x33333333333333) + ((y >> 2) & 0x33333333333333)
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    ส่งคืน x & 0xFF;
}

ดูเหมือนว่าถูกต้อง (ฉันไม่ได้ทดสอบอย่างรอบคอบ) ตอนนี้เวลาออกมาที่ 10.70 gigacycles / 14.1 gigacycles หมายเลขนั้นในภายหลังนั้นรวม 128 พันล้านบิตและสอดคล้องกับ 5.9 วินาทีที่ผ่านไปในเครื่องนี้ รุ่นที่ไม่ขนานกันเร็วขึ้นเล็กน้อยเพราะฉันทำงานในโหมด 64 บิตและชอบการลงทะเบียน 64 บิตดีกว่าการลงทะเบียน 32 บิตเล็กน้อย

เรามาดูกันว่ามีการวางท่อ OOO อีกเล็กน้อยที่นี่หรือไม่ นี่เกี่ยวข้องกับอีกเล็กน้อยดังนั้นฉันจึงทดสอบจริง ๆ เล็กน้อย แต่ละเทอมรวมอยู่ที่ 64 รวมทั้งหมดเป็น 256

inline int pop4 (ยาวไม่ลงนาม x, ไม่ได้ลงนามยาว y, 
                ไม่ได้ลงนามยาวคุณ v ไม่ลงนามยาว)
{
  enum {m1 = 0x5555555555555555 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F0F, 
         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 play tricks หนึ่งพันล้านการเรียกไปยัง pop4 () ใช้เวลา 12.56 gigacycles แต่ฉันคิดว่ามันเป็นอาร์กิวเมนต์การพับเป็นนิพจน์คงที่ จำนวนที่เหมือนจริงมากขึ้นดูเหมือนจะเป็น 19.6gc สำหรับความเร็วอีก 30% ตอนนี้ลูปทดสอบของฉันมีลักษณะเช่นนี้ตรวจสอบให้แน่ใจว่าแต่ละอาร์กิวเมนต์แตกต่างกันพอที่จะหยุด gcc ไม่ให้เล่นกล

   hitime b4 = rdtsc (); 
   สำหรับ (ความยาวที่ไม่ได้ลงชื่อ i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000 * 1000 i ++) 
      sum + = pop4 (i, i ^ 1, ~ i, i | 1); 
   hitime e4 = rdtsc (); 

256,000 ล้านบิตรวมกันใน 8.17s ที่ผ่านไป ทำงานได้ถึง 1.02 วินาทีสำหรับ 32 ล้านบิตตามมาตรฐานในการค้นหาตาราง 16 บิต ไม่สามารถเปรียบเทียบได้โดยตรงเนื่องจากม้านั่งตัวอื่นไม่ให้ความเร็วสัญญาณนาฬิกา แต่ดูเหมือนว่าฉันตบน้ำมูกออกจากตารางรุ่น 64KB ซึ่งเป็นการใช้ L1 แคชที่น่าเศร้าในตอนแรก

อัปเดต: ตัดสินใจที่จะทำอย่างชัดเจนและสร้าง pop6 () โดยเพิ่มบรรทัดที่ซ้ำกันอีกสี่บรรทัด ออกมาที่ 22.8gc, 384 พันล้านบิตรวมกันใน 9.5 วินาทีที่ผ่านไป มีอีก 20% ตอนนี้ที่ 800 มิลลิวินาทีสำหรับ 32 พันล้านบิต


2
รูปแบบที่ไม่ใช่แอสเซมเบลอร์ที่ดีที่สุดเช่นนี้ฉันเคยเห็นคำที่ไม่มีการควบคุม 24 32 บิตในแต่ละครั้ง dalkescientific.com/writings/diary/popcnt.c , stackoverflow.com/questions/3693981/… , dalkescientific.com/writings/diary/archive/2008/07/05/…
แมตต์ช่างไม้

28

ทำไมไม่หาร 2 ซ้ำ ๆ กัน?

count = 0
ในขณะที่ n> 0
  if (n% 2) == 1
    นับ + = 1
  n / = 2  

ฉันยอมรับว่านี่ไม่ใช่วิธีที่เร็วที่สุด แต่ "ดีที่สุด" นั้นค่อนข้างคลุมเครือ ฉันขอยืนยันว่า "ดีที่สุด" ควรมีองค์ประกอบของความชัดเจน


มันจะใช้งานได้และเข้าใจง่าย แต่ก็มีวิธีการที่เร็วกว่า
Matt Howells

2
เว้นแต่คุณจะทำสิ่งนี้มากผลกระทบด้านประสิทธิภาพจะไม่สำคัญ ดังนั้นทุกสิ่งเท่าเทียมกันฉันเห็นด้วยกับแดเนียลว่า 'ดีที่สุด' หมายถึง "ไม่อ่านเหมือนคำพูดไร้สาระ"

2
ฉันตั้งใจไม่ได้นิยาม 'ดีที่สุด' เพื่อให้ได้วิธีการที่หลากหลาย ให้หน้ามันถ้าเราลงไปถึงระดับของการบิดนิด ๆ หน่อย ๆ แบบนี้เราอาจกำลังมองหาบางสิ่งที่รวดเร็ว - uber ที่ดูเหมือนว่าชิมแปนซีได้พิมพ์มัน
Matt Howells

6
รหัสไม่ถูกต้อง คอมไพเลอร์อาจทำออกมาได้ดี แต่ในการทดสอบของฉัน GCC ทำไม่ได้ แทนที่ (n% 2) ด้วย (n & 1); และเร็วกว่า MODULO มาก แทนที่ (n / = 2) ด้วย (n >> = 1); บิตการเคลื่อนที่เร็วกว่าการหารมาก
Mecki

6
@Mecki: ในการทดสอบของฉัน gcc (4.0, -O3) ได้ทำการปรับให้เหมาะสมอย่างเห็นได้ชัด

26

Bit-Twiddling ของ Delight นั้นชัดเจนมากขึ้นเมื่อคุณเขียนรูปแบบบิต

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) & 0b0101010101010101010101010101010101) + (x & 0b010101010101010101010101010101010101)); (เช่นเพิ่ม parens พิเศษ)
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 จริงเหมือนที่ออกมี ...
โรเบิร์ตเอสบาร์นส์

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

ฟังก์ชั่นที่คุณกำลังค้นหามักเรียกว่า "ผลรวมด้านข้าง" หรือ "จำนวนประชากร" ของเลขฐานสอง Knuth กล่าวถึงใน Pre-Fascicle 1A, pp11-12 (แม้ว่าจะมีการอ้างอิงสั้น ๆ ในเล่ม 2, 4.6.3- (7))

classicus สถานทีเป็นบทความปีเตอร์เวกเนอร์ของ "เทคนิคสำหรับคนนับในไบนารีคอมพิวเตอร์ A" จากการสื่อสารของพลอากาศเอกเล่ม 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 ครั้ง

เราสามารถแก้ไข algo เพื่อรองรับจำนวนลบดังนี้: -

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

ตอนนี้เพื่อเอาชนะปัญหาที่สองที่เราสามารถเขียนอัลโก้เช่น: -

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 ครั้งที่ 2 (โดย Brian W. Kernighan และ Dennis M. Ritchie) กล่าวถึงเรื่องนี้ในการออกกำลังกาย 2-9 ในวันที่ 19 เมษายน 2549 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

PS: ฉันรู้ว่านี่ไม่ใช่ทางออก O (1) แม้ว่าจะเป็นทางออกที่น่าสนใจ


นี้เป็นสิ่งที่ดีสำหรับตัวเลข "เบาบาง" O(ONE-BITS)ที่มีจำนวนต่ำของบิตตามที่มันเป็น เป็น O (1) เนื่องจากมีอย่างน้อย 32 บิต
ealfonso

7

คุณหมายความว่าอย่างไรกับ "อัลกอริทึมที่ดีที่สุด" รหัสย่อหรือรหัส fasted? รหัสของคุณดูดีมากและมีเวลาดำเนินการอย่างต่อเนื่อง รหัสนี้สั้นมาก

แต่ถ้าความเร็วเป็นปัจจัยสำคัญและไม่ใช่ขนาดรหัสฉันคิดว่าการติดตามจะเร็วขึ้น:

       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

วิธีนี้สามารถวัดได้เร็วกว่าวิธีแบบ twiddling เล็กน้อยในขณะที่มันปรากฏออกมา สำหรับการใช้หน่วยความจำเพิ่มเติมมันจะรวบรวมรหัสให้น้อยลงและจะได้รับซ้ำทุกครั้งที่คุณฟังก์ชั่นอินไลน์ ดังนั้นมันสามารถกลายเป็นชัยชนะสุทธิได้อย่างง่ายดาย

7

ฉันเขียนมาโคร bitcount อย่างรวดเร็วสำหรับเครื่อง RISC ในประมาณปี 1990 มันไม่ได้ใช้เลขคณิตขั้นสูง (การคูณการหาร%) การดึงหน่วยความจำ (ทางช้าเกินไป) สาขา (ช้าเกินไป) แต่ถือว่า CPU มี ตัวเปลี่ยนบาร์เรลแบบ 32 บิต (กล่าวอีกนัยหนึ่ง >> 1 และ >> 32 ใช้จำนวนรอบเท่ากัน) ถือว่าค่าคงที่ขนาดเล็ก (เช่น 6, 12, 24) ไม่มีค่าใช้จ่ายใด ๆ ในการโหลดลงทะเบียนหรือถูกเก็บไว้ ในชั่วขณะและนำกลับมาใช้ซ้ำแล้วซ้ำอีก

ด้วยสมมติฐานเหล่านี้มันนับ 32 บิตในประมาณ 16 รอบ / คำสั่งในเครื่อง RISC ส่วนใหญ่ โปรดทราบว่า 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

ดังนั้นถ้าฉันนำคอลัมน์ที่ 1 (A) ด้านบนเลื่อนไปทางขวา 1 บิตแล้วลบออกจาก AB ฉันจะได้ผลลัพธ์ (CD) ส่วนขยายถึง 3 บิตคล้ายกัน คุณสามารถตรวจสอบกับตารางบูลีน 8 แถวเหมือนของฉันด้านบนหากคุณต้องการ

  • ดอนกิลลี

7

หากคุณใช้ C ++ ตัวเลือกอื่นคือการใช้ metaprogramming แม่แบบ:

// 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

ฉันชอบตัวอย่างนี้โดยเฉพาะจากไฟล์โชค:

#define BITCOUNT (x) ((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F0F)% 255)
#define BX_ (x) ((x) - ((x) >> 1) & 0x77777777)
                             - (((x) >> 2) & 0x33333333)
                             - (((x) >> 3) & 0x11111111))

ฉันชอบที่สุดเพราะมันสวยมาก!


1
มันทำงานอย่างไรเมื่อเทียบกับข้อเสนอแนะอื่น ๆ ?
asdf

6

Java 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 อย่างไรก็ตามเนื่องจากผู้พัฒนา Java บางรายอาจไม่ทราบวิธีการนี้
finnw

@finnw ฉันเป็นหนึ่งในนักพัฒนาเหล่านั้น :)
neevek

6

ฉันพบการใช้งานการนับบิตในอาร์เรย์ด้วยการใช้คำสั่ง SIMD (SSSE3 และ AVX2) มันมีประสิทธิภาพที่ดีขึ้นกว่า 2-2.5 เท่ากว่าถ้ามันจะใช้ __ popcnt64 ฟังก์ชั่นที่แท้จริง

รุ่น 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

ฉันมักจะใช้สิ่งนี้ในการเขียนโปรแกรมการแข่งขันและง่ายต่อการเขียนและมีประสิทธิภาพ:

#include <bits/stdc++.h>

using namespace std;

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

5

มีอัลกอริทึมมากมายในการนับบิตตั้ง แต่ฉันคิดว่าดีที่สุดคือเร็วกว่า! คุณสามารถดูรายละเอียดในหน้านี้:

Bit Twiddling Hacks

ฉันแนะนำอันนี้:

การนับจำนวนบิตที่ตั้งค่าเป็น 14, 24 หรือ 32- บิตโดยใช้คำสั่ง 64- บิต

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;

วิธีนี้ต้องใช้ CPU 64 บิตที่มีการแบ่งโมดูลัสอย่างรวดเร็วเพื่อให้มีประสิทธิภาพ ตัวเลือกแรกใช้เวลาเพียง 3 การดำเนินการ; ตัวเลือกที่สองใช้เวลา 10; และตัวเลือกที่สามใช้เวลา 15


5

วิธีแก้ปัญหา Fast 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

ตารางที่ใหญ่กว่าอาจช้ากว่า (และไม่ใช่เวลาคงที่) เนื่องจากปัญหาแคช คุณสามารถ 'มองขึ้น' 3 บิตในขณะที่มี(0xe994 >>(k*2))&3โดยไม่ต้องเข้าถึงหน่วยความจำ ...
greggo

5

นี่คือโมดูลพกพา (ANSI-C) ซึ่งสามารถวัดมาตรฐานของอัลกอริทึมของคุณในสถาปัตยกรรมใด ๆ

CPU ของคุณมีขนาด 9 บิตหรือไม่ ไม่มีปัญหา :-) ในขณะนี้ใช้ 2 อัลกอริทึม K&R อัลกอริทึมและตารางการค้นหาไบต์ที่ชาญฉลาด ตารางการค้นหานั้นเร็วกว่าอัลกอริทึม K&R โดยเฉลี่ย 3 เท่า หากใครบางคนสามารถหาวิธีที่จะทำให้อัลกอริทึม "แฮปปี้ดีไลท์" แบบพกพารู้สึกฟรีเพื่อเพิ่มมัน

#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 ในการวนซ้ำทุกครั้งและไปที่บิตขวาสุดถัดไปเสมอนับนับบิตเซตความซับซ้อนของเวลาที่เลวร้ายที่สุดจะเป็น O (logn) เมื่อตั้งค่าทุกบิต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.