Як порахувати кількість встановлених бітів у 32-бітовому цілому?


868

8 біт, що представляють число 7, виглядають так:

00000111

Встановлено три біти.

Які алгоритми визначають кількість встановлених бітів у 32-бітовому цілому?


101
Це вага Хеммінга BTW.
Purfideas

11
Що для цього в реальному застосуванні? (Це не слід сприймати як критику - мені просто цікаво.)
jonmorgan

8
Розрахунок біту парності (подивіться його), який використовувався як просте виявлення помилок у спілкуванні.
Діалектик

8
@Dialecticus, обчислити біт паритету дешевше, ніж обчислити вагу Хеммінга
finnw

15
@spookyjon Скажімо, у вас є графік, представлений у вигляді матриці суміжності, яка по суті є набором. Якщо ви хочете обчислити кількість ребер вершини, вона зводиться до обчислення ваги Хеммінга одного ряду в наборі біт.
fuz

Відповіді:


850

Це відомо як " Вага Хеммінга ", "Попконт" або "Додаток убік".

Алгоритм "найкращого" дійсно залежить від того, на якому процесорі ви знаходитесь, і якою є ваша схема використання.

Деякі процесори мають вбудовану інструкцію робити це, а інші мають паралельні інструкції, які діють на бітові вектори. Паралельні інструкції (як x86 popcnt, на процесорах, де вони підтримуються) майже напевно будуть найшвидшими. Деякі інші архітектури можуть мати повільну інструкцію, реалізовану з мікрокодованим циклом, який тестує біт за цикл ( необхідне цитування ).

Метод пошуку заздалегідь заповненої таблиці може бути дуже швидким, якщо ваш процесор має великий кеш-пам'ять і / або ви виконуєте багато цих інструкцій у вузькому циклі. Однак це може страждати через рахунок "пропуску кешу", де процесор повинен отримати частину таблиці з основної пам'яті. (Знайдіть кожен байт окремо, щоб таблиця була маленькою.)

Якщо ви знаєте, що ваші байти будуть здебільшого 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 бітхак:

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

Перший крок - це оптимізована версія маскування для виділення непарних / парних бітів, переведення їх на лінійку та додавання. Це ефективно робить 16 окремих доповнень у 2-бітових акумуляторах ( SWAR = SIMD у межах реєстру ). Як (i & 0x55555555) + ((i>>1) & 0x55555555).

Наступний крок займає непарний / парний вісім з цих 16x 2-бітних акумуляторів і знову додає, виробляючи 8x 4-бітні суми. i - ...Оптимізація не представляється можливим в цей раз , так це просто маскувати до / після зсуву. Використання однієї 0x33...і тієї ж константи обох разів замість 0xccc...перед зміщенням - це добре при компілюванні для ISA, яким потрібно конструювати 32-бітні константи в регістрах окремо.

Заключний крок "зміщення і додавання" (i + (i >> 4)) & 0x0F0F0F0Fрозширюється до 4-х 8-бітових акумуляторів. Він маскується після додавання замість попереднього, оскільки максимальне значення в будь-якому 4-бітовому акумуляторі є 4, якщо всі 4 біти відповідних вхідних бітів були встановлені. 4 + 4 = 8, який все ще вкладається в 4 біти, тому переносити між елементами прикування неможливо i + (i >> 4).

Поки що це просто нормальна SIMD з використанням методів SWAR з кількома розумними оптимізаціями. Продовжуючи з тим же малюнком ще 2 кроки, можна розширитись до 2x 16-розрядних, а потім 1-х 32-розрядних. Але є більш ефективний спосіб на машинах із швидким розмноженням апаратних засобів:

Після того, як у нас є досить мало "елементів", множення на магічну константу може підбити всі елементи до верхнього елемента . У цьому випадку елементи байта. Множення відбувається за допомогою зсуву вліво і додавання, тож множення x * 0x01010101результатів у x + (x<<8) + (x<<16) + (x<<24). Наші 8-бітові елементи досить широкі (і містять достатньо малі рахунки), що не призводить до того, що вони входять в перші 8 біт.

64-розрядна версія цього може робити 8x 8-бітні елементи в 64-бітовому цілому з множником 0x0101010101010101 і витягувати високий байт за допомогою >>56. Тож не потрібно робити зайвих кроків, лише ширші константи. Це те, що GCC використовує для __builtin_popcountllсистем x86, коли popcntінструкція з обладнання не включена. Якщо ви можете використовувати для цього вбудовані або внутрішні символи, зробіть це, щоб дати шанс компілятору зробити оптимізацію, орієнтовану на ціль.


З повною SIMD для більш широких векторів (наприклад, підрахунок цілого масиву)

Цей бітовий алгоритм SWAR міг би паралелізуватись, що робиться відразу в декількох векторних елементах, а не в єдиному цілому регістрі, для прискорення роботи процесорів із SIMD, але не використовуваною інструкцією з попконтатом. (наприклад, код x86-64, який повинен працювати на будь-якому процесорі, а не лише на Nehalem чи новіших версіях).

Однак найкращий спосіб використовувати векторні вказівки для popcount - це зазвичай за допомогою змінної перетасовки робити пошук таблиці за 4 біти одночасно кожного байту паралельно. (4 біти індексують 16 вхідних таблиць, що знаходяться у векторному регістрі).

На процесорах Intel, інструкція POPCNT апаратного 64bit може випереджати в SSSE3 PSHUFBбіт-паралельна реалізація приблизно в 2 рази, але тільки якщо ваш компілятор отримує це тільки право . Інакше SSE може вийти значно попереду. Новіші версії компілятора відомі з проблемою помилкової залежності popcnt від Intel .

Список літератури:


87
га! люблю функцію NumberOfSetBits (), але удачі отримати це через огляд коду. :-)
Джейсон S

37
Можливо, це слід використовувати unsigned int, щоб легко показати, що воно не містить будь-яких ознак бітових ускладнень. Крім того, було uint32_tб безпечніше, як в, ви отримуєте те, що очікуєте на всіх платформах?
Крейг МакКуїн

35
@nonnb: Насправді, як написано, код баггі і потребує обслуговування. >>визначається реалізацією для негативних значень. Аргумент потрібно змінити (або видати) на unsigned, а оскільки код є 32-бітовим, він, ймовірно, використовує uint32_t.
R .. GitHub СТОП ДОПОМОГАЙТЕ

6
Це насправді не магія. Це додавання наборів бітів, але це робиться з деякими розумними оптимізаціями. Посилання на вікіпедію, дане у відповіді, добре допомагає пояснити, що відбувається, але я піду по черзі. 1) Підраховуйте кількість бітів у кожній парі бітів, вводячи це число в цю пару біт (у вас буде 00, 01 або 10); "розумний" біт тут - віднімання, що уникає однієї маски. 2) Додайте пари цих сум бітпарів до відповідних ніблів; тут немає нічого розумного, але кожен клювач тепер матиме значення 0-4. (продовження)
dash-tom-bang

8
Ще одна примітка: це поширюється на 64 та 128 бітові регістри, просто збільшуючи константи належним чином. Цікаво (для мене), що ці константи також ~ 0/3, 5, 17 і 255; колишня три - 2 ^ n + 1. Це все має сенс, чим більше ви дивитесь на нього і думаєте про це під душем. :)
dash-tom-bang

214

Також врахуйте вбудовані функції ваших компіляторів.

Наприклад, у компіляторі GNU ви можете просто використовувати:

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

У гіршому випадку компілятор генерує виклик функції. У кращому випадку компілятор передасть інструкцію процесора, щоб виконати ту саму роботу швидше.

Властивості GCC навіть працюють на багатьох платформах. Popcount стане основним в архітектурі x86, тому має сенс почати використовувати внутрішнє. Інші архітектури роками попконт.


На x86 ви можете сказати компілятору, що він може взяти на себе підтримку popcntінструкцій -mpopcntабо -msse4.2також включити векторні інструкції, додані в тому ж поколінні. Див. Параметри GCC x86 . -march=nehalem(або -march=будь-який процесор, який ви хочете, щоб ваш код був прийнятий і налаштований), може бути хорошим вибором. Запуск отриманого двійкового файлу на старшому процесорі призведе до помилки з незаконною інструкцією.

Щоб оптимізовані бінарні файли для машини, на якій ви будуєте, використовуйте -march=native (з gcc, clang або ICC).

MSVC забезпечує властивість popcntінструкції x86 , але, на відміну від gcc, це справді властива інструкція по апаратному забезпеченню і вимагає апаратної підтримки.


Використання std::bitset<>::count()замість вбудованого

Теоретично, будь-який компілятор, який знає, як ефективно виконувати ефективність для цільового процесора, повинен відкривати цю функціональність через ISO C ++ std::bitset<>. На практиці у деяких випадках для деяких цільових процесорів вам може бути краще бит-хак AND / shift / ADD.

Для цільових архітектур, де апаратний попконт є необов'язковим розширенням (наприклад, x86), не всі компілятори мають таке, std::bitsetщо користується ним, коли воно доступне. Наприклад, MSVC не має можливості ввімкнути popcntпідтримку під час компіляції, і завжди використовує пошук таблиці навіть з /Ox /arch:AVX(що передбачає SSE4.2, хоча технічно існує окремий біт функції 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();
}

Дивіться 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 для архітектур без одноразового попконтату - це пошук таблиці за байтом за часом. Наприклад, це не чудово для ARM .


5
Я погоджуюсь, що це взагалі хороша практика, але в XCode / OSX / Intel я виявив, що він генерує повільніший код, ніж більшість пропозицій, розміщених тут. Детальну інформацію див. У моїй відповіді.

5
Intel i5 / i7 має інструкцію POPCNT для SSE4, яка виконує це, використовуючи регістри загального призначення. GCC в моїй системі не дає цієї інструкції, використовуючи цю внутрішню, я думаю, через відсутність параметра -march = nehalem.
matja

3
@matja, мій GCC 4.4.1 видає інструкцію popcnt, якщо я компілюю з -msse4.2
Nils Pipenbrinck

74
використовувати c ++ 's std::bitset::count. після вбудовування цього компілюється до одного __builtin_popcountдзвінка.
deft_code

1
@nlucaroni Ну, так. Часи змінюються. Цю відповідь я написав у 2008 році. На сьогоднішній день у нас є власний попконт, і внутрішня версія складеться до однієї заяви асемблера, якщо платформа це дозволяє.
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 і коментувати це як "біт змін ...", вам слід просто скористатися оператором зміни (>>) і залишити коментар.
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-іш інструкціях (залежно від арки), без розгалуження.

Хакерське захоплення - чудове! Настійно рекомендується.


8
Метод Java Integer.bitCount(int)використовує таку саму точну реалізацію.
Марко Боліс

Виникли невеликі проблеми після цього - як це зміниться, якщо ми піклувалися б лише про 16-бітні значення, а не про 32-бітні?
Джеремі Блюм

Можливо, хакерам це захоплення приємно, але я б добре вдарив когось, хто закликає це popзамість population_count(або pop_cntякщо у вас повинна бути відмова). @MarcoBolis Я припускаю, що це стосується всіх версій Java, але офіційно це залежатиме від впровадження :)
Maarten Bodewes

І це не потребує множення, як код у прийнятій відповіді.
Олексій

Зауважте, що в узагальненні до 64-бітових є проблема. Результат не може бути 64 через маску.
Альберт ван дер Хорст

76

Я думаю, що найшвидший спосіб - без використання таблиць пошуку та попконтату - наступний. Він рахує встановлені біти всього за 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); 

Це твердження повинно бути легко зрозуміти. Після першої операції ми маємо підрахунок встановлених бітів кожні два біти, тепер ми підсумовуємо це число у кожні 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

Це дає нам підрахунок встановлених бітів у байті, в першому nibble, 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 = ? Схоже, це слід усунути. Крім того, запропонуйте додатковий батьківський набір A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24", щоб уникнути класичних попереджень.
chux

4
Важливою особливістю є те, що ця 32-розрядна рутина працює і для, popcount(int v)і для popcount(unsigned v). Для портативності розгляньте popcount(uint32_t v)тощо. Дійсно як деталь * 0x1010101.
chux

соус? (книга, посилання, назви інвесторів тощо) були б ДУЖЕ вітаються. Тому що тоді ми можемо вставити це в наші кодові бази з коментарем, звідки воно походить.
v.oddou

1
Я думаю, що для більшої ясності останній рядок повинен бути записаний так: return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;тому нам не потрібно рахувати літери, щоб побачити, що ви насправді робите (оскільки ви відкинули перший 0, я випадково подумав, що ви використали неправильну (перевернуту) бітову схему як маску - тобто поки я не зазначив, що всього 7 букв, а не 8).
emem

Це множення на 0x01010101 може бути повільним, залежно від процесора. Наприклад, у моєму старому PowerBook G4 1 множення було приблизно таким же повільним, як і 4 доповнення (не так погано, як поділ, де 1 поділ був приблизно таким же повільним, як і 23 доповнення).
Джордж Келер

54

Мені нудно і приурочили мільярд ітерацій трьох підходів. Компілятор - gcc -O3. Процесор - це все, що вони ставлять у 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] );
}

Друге місце займає той самий код, але шукає 4 байти замість двох півслов. На це пішло близько 5,5 секунд.

Третє місце посідає бит-подвійний підхід «набік додавання», який зайняв 8,6 секунди.

Четверте місце займає __builtin_popcount () GCC за ганебні 11 секунд.

Підхід підрахунку один раз за часом був повільнішим, і мені нудно було чекати його завершення.

Тож якщо ви переймаєтесь ефективністю понад усе, тоді використовуйте перший підхід. Якщо вам все одно, але недостатньо витратити на це 64 Кб оперативної пам’яті, скористайтеся другим підходом. В іншому випадку використовуйте читаний (але повільний) підхід один раз за часом.

Важко придумати ситуацію, коли ви хочете скористатись підходом біт-подвійності.

Редагувати: Подібні результати тут .


49
@Mike, Підхід на основі таблиці є неперевершеним, якщо таблиця знаходиться в кеші. Це відбувається в мікро-орієнтирах (наприклад, робити мільйони тестів у тісному циклі). Однак пропуск кеша займає близько 200 циклів, і навіть найнаївніший попконт тут буде швидше. Це завжди залежить від програми.
Нілс Піпенбрінк

10
Якщо ви не називаєте цю рутину кілька мільйонів разів у тісному циклі, то у вас взагалі немає причин дбати про її ефективність, і ви могли б також використовувати підхід, який можна прочитати наївно, але читати, оскільки втрата продуктивності буде незначною. І FWIW, 8bit LUT отримує кеш-запас протягом 10-20 дзвінків.

6
Я не думаю, що все так складно уявити ситуацію, коли у вашому додатку це листковий дзвінок, зроблений за допомогою методу - фактично це робить важкий підйом. Залежно від того, що ще відбувається (і нанизування), менша версія може виграти. Було написано безліч алгоритмів, які б'ють своїх ровесників завдяки кращій локальній орієнтації. Чому б і цього не зробити?
Джейсон

Спробуйте це з clang, це значно розумніше в реалізації вбудованих.
Метт Столяр

3
GCC не передаватиме інструкцію попконт, якщо не буде викликано -msse4.2, випадок, який швидше, ніж "додавання в бік".
lvella

54

Якщо ви випадково використовуєте Java, вбудований метод Integer.bitCountзробить це.


Коли сонце надало різні API, воно повинно використовувати певну логіку на тлі, правда?
Валлаб Патаде

2
В якості додаткової примітки, реалізація Java використовує той самий алгоритм, на який вказував Кевін Літл .
Марко Боліс

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
Цей алгоритм - це версія, яку розмістив Метт Хоуеллс, перш ніж її оптимізували до того, що вона стала нечитабельною.
Lefteris E

29

Це одне з тих питань, де це допомагає пізнати вашу мікро-архітектуру. Я щойно приуротив два варіанти під gcc 4.3.3, складений з -O3 з використанням рядків C ++ для усунення накладних викликів функцій, один мільярд ітерацій, зберігаючи поточну суму всіх підрахунків, щоб переконатися, що компілятор не видаляє нічого важливого, використовуючи 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 гігацикла. Моя паралельна версія (рахує вдвічі більше біт) працює в 13,0 гігациклів. 10,5 сек загалом минуло для обох разом на 2,4 ГГц Core Duo. 25 гігациклів = трохи більше 10 секунд на цій тактовій частоті, тож я впевнений, що мої терміни є правильними.

Це пов'язано з ланцюгами залежностей від інструкцій, що дуже погано підходить для цього алгоритму. Я міг майже подвоїти швидкість знову за допомогою пари 64-розрядних регістрів. Насправді, якби я був розумним і додавав х + я трохи раніше, я міг би погладити деякі зміни. 64-розрядна версія з невеликими налаштуваннями вийшла б рівномірною, але знову рахуйте вдвічі більше бітів.

З 128-бітовими регістрами SIMD, ще один фактор два, і набори інструкцій SSE часто мають і розумні скорочення.

Немає причини, щоб код був особливо прозорим. Інтерфейс простий, на алгоритм можна посилатися он-лайн у багатьох місцях, і він піддається всебічному тестуванню одиниць. Програміст, який натрапляє на нього, може навіть чомусь навчитися. Ці бітові операції надзвичайно природні на рівні машини.

Гаразд, я вирішив стендувати 64-бітну версію, яку було налаштовано. Для цього один розмірofof (неподписаний довгий) == 8

вбудований int pop2 (неподписаний довгий х, неподписаний довгий 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)) & 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 гігациклів / 14,1 гігациклів. Це число пізніше становило 128 мільярдів біт і відповідає 5,9 секунди, що минула на цій машині. Непаралельна версія прискорює невеликий біт, тому що я працюю в 64-бітному режимі і йому подобаються 64-бітні регістри трохи краще, ніж 32-бітні регістри.

Давайте подивимось, чи буде тут трохи більше трубопроводів ТОВ. Це було трохи більше, тому я фактично трохи перевірив. Кожен окремий термін становить 64, а сумарна сума - 256.

вбудований int pop4 (неподписаний довгий х, неподписаний довгий y, 
                неподписаний довгий u, неподписаний довгий v)
{
  перерахунок {m1 = 0x5555555555555555, 
         м2 = 0x3333333333333333, 
         м3 = 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, хоча я не використовую ключове слово inline в деяких тестах. Коли я дозволяю gcc виконувати трюки, мільярд дзвінків до pop4 () займає 12,56 гігациклів, але я визначив, що це складні аргументи як постійні вирази. Здається, більш реалістичним є 19,6 гс для ще 30% -го збільшення швидкості. Мій тестовий цикл зараз виглядає так, переконуючись, що кожен аргумент є досить різним, щоб зупинити 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 (); 

Пройшло 256 мільярдів біт, підсумованих за 8,17 секунд. Працює до 1,02 для 32 мільйонів біт, як це відмічено в пошуку 16-бітної таблиці. Неможливо порівняти безпосередньо, тому що інша лава не дає тактової швидкості, але виглядає так, що я вирвав соплі з видання таблиці 64 КБ, що в першу чергу є трагічним використанням кешу L1.

Оновлення: вирішили зробити очевидне і створити pop6 (), додавши ще чотири дублюються рядки. Вийшло 22,8 гс, пройшло 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 / ...
Matt Joiner

28

Чому б не ітераційно розділити на 2?

count = 0
при цьому n> 0
  якщо (n% 2) == 1
    підрахунок + = 1
  n / = 2  

Я згоден, що це не найшвидше, але "найкраще" є дещо неоднозначним. Я б заперечував, що "найкращий" повинен мати елемент ясності


Це буде працювати і легко зрозуміти, але є більш швидкі методи.
Метт Хоуеллс

2
Якщо ви не зробите це БАГАТО , вплив на продуктивність буде незначним. Таким чином, при рівності, я погоджуюся з Даніелем, що "найкраще" означає, що "не читається, як хитрість".

2
Я навмисно не визначав "найкращий", щоб отримати різноманітні методи. Давайте зможемо зіткнутися з цим, якщо ми зійшли на рівень подібного роду біт-скручування, ми, мабуть, шукаємо щось убер-швидке, схоже на шимпанзе.
Метт Хоуеллз

6
Неправильний код. Компілятор може зробити з цього хороший, але в моїх тестах GCC цього не зробив. Замініть (n% 2) на (n & 1); І бути набагато швидшим, ніж МОДУЛО. Замініть (n / = 2) на (n >> = 1); бітшіфтінг набагато швидше, ніж поділ.
Mecki

6
@Mecki: У моїх тестах gcc (4.0, -O3) зробив очевидні оптимізації.

26

Біт-подвійне подяка Hacker's 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;
}

Перший крок додає парні біти до непарних бітів, отримуючи суму бітів у кожному з двох. Інші кроки додають шматки високого порядку до шматок низького порядку, удвічі збільшуючи розмір шматка вгору, поки ми не отримаємо остаточний підрахунок, який займає всю цілу.


3
Це рішення, мабуть, має незначну проблему, пов'язану з перевагою оператора. Для кожного терміна слід сказати: x = (((x >> 1) & 0b0101010101010101010101010101010101) + (x & 0b0101010101010101010101010101010101)); (тобто додані додаткові паролі).
Нопік

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


Не портативний. Що робити, якщо в процесорі є 9 бітних байтів? Так, там є справжні CPU ...
Роберт С. Барнс

15
@ Роберт С. Барнс, ця функція все ще буде працювати. Це не передбачає припущення про розмір рідного слова і взагалі не посилається на "байти".
finnw

19

Це можна зробити в O(k), де kвстановлено кількість бітів.

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

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

    return count;
}

Це по суті алгоритм Брайана Кернігана (пам'ятаєте його?), При незначній зміні він використовував більш стисну n &= (n-1)форму.
Адріан Моль

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
о, мені це подобається. як битися з версією python: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

Функцію, яку ви шукаєте, часто називають "бічною сумою" або "кількістю населення" двійкового числа. Кнут обговорює це в передфабриці 1A, стор.11-12 (хоча в Томі 2, 4.6.3- (7) було коротке посилання).

Локус Classicus є стаття Пітера Вегнера «Методика для підрахунку одиниць в двійковому комп'ютері», від комунікацій ACM , том 3 (1960) Номер 5, стор 322 . Він надає два різні алгоритми, один оптимізований для чисел, які, як очікується, "розріджені" (тобто мають невелику кількість одиниць), а один для протилежного випадку.


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

9

Кілька відкритих питань: -

  1. Якщо число від’ємне, то?
  2. Якщо число дорівнює 1024, то метод "ітераційно розділити на 2" повториться в 10 разів.

ми можемо змінити альго, щоб підтримувати від'ємне число наступним чином:

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

Тепер для подолання другої проблеми ми можемо записати algo на зразок: -

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

для повної довідки див .:

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


9

Я думаю, що метод Брайана Керніган теж буде корисним ... Він проходить через стільки ітерацій, скільки встановлених біт. Отже, якщо у нас є 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 р., Мова програмування С 2-е видання. (Брайан В. Керніган та Денніс М. Річі) згадує це у вправі 2-9. 19 квітня 2006 р. Дон Кнут вказав мені, що цей метод "вперше був опублікований Пітером Вегнером у CACM 3 (1960), 322. (Також його відкрив незалежно Деррік Лемер та опублікував у 1964 р. У книзі за редакцією Бекенбаха.)"


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

Що ви розумієте під "Найкращим алгоритмом"? Короткий код або швидкий код? Ваш код виглядає дуже елегантно, і він має постійний час виконання. Код також дуже короткий.

Але якщо швидкість є головним фактором, а не розміром коду, то я думаю, що наступне може бути швидшим:

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

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

Я думаю, що це не буде швидше для 64-бітного значення, але 32-бітове значення може бути швидшим.


Мій код має 10 операцій. Ваш код має 12 операцій. Ваше посилання працює з меншими масивами (5). Я використовую 256 елементів. З кешуванням може бути проблема. Але якщо ви використовуєте його дуже часто, то це не проблема.
Horcrux7

Цей підхід вимірюється досить трохи швидше, ніж біт-подвійний підхід, як виявляється. Що стосується використання більшої кількості пам'яті, вона компілюється з меншим кодом, і це посилення повторюється щоразу, коли ви вбудовуєте функцію. Тож це може легко виявитись чистим виграшем.

7

Я написав швидкий макрос bitcount для машин RISC приблизно в 1990 році. Він не використовує розширену арифметику (множення, поділ,%), витяг пам'яті (занадто повільно), гілки (занадто повільно), але він припускає, що процесор має 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-й стовпчик (А) вище, зміщую його вправо на 1 біт і віднімаю його від AB, я отримую вихід (CD). Розширення до 3 біт аналогічне; Ви можете перевірити це за допомогою 8-рядкової булевої таблиці, як моя вище, якщо бажаєте.

  • Дон Джіллі

7

якщо ви використовуєте C ++, ще одним варіантом є використання метапрограмування шаблонів:

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

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

використання буде:

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

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

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

можна, звичайно, ще більше розширити цей шаблон, щоб використовувати різні типи (навіть автоматичне виявлення розміру бітів), але я зрозумів, що це просто для ясності.

редагувати: забув згадати це добре, тому що він повинен працювати в будь-якому компіляторі C ++, і він в основному просто розкручує ваш цикл для вас, якщо для підрахунку бітів використовується постійне значення (іншими словами, я впевнений, що це найшвидший загальний метод ти знайдеш)


На жаль, підрахунок бітів не проводиться паралельно, тому, ймовірно, повільніше. Можливо, приємно constexprхоч.
imallett

Домовились - це було цікаве вправлення в рекурсії шаблонів C ++, але, безумовно, досить наївне рішення.
пентафоб

6

Мені особливо подобається цей приклад з файлу fortune:

#define BITCOUNT (x) (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 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, не стільки для всіх інших.
бензадо

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

Існує багато алгоритму підрахунку встановлених бітів; але я думаю, що найкращий - швидший! На цій сторінці ви можете ознайомитись детально:

Біт Twiddling хакі

Я пропоную це:

Підрахунок бітів, встановлених у 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;

Цей метод вимагає ефективності 64-бітного процесора з швидким поділом модуля. Перший варіант займає лише 3 операції; другий варіант займає 10; а третій варіант займає 15.


5

Швидке рішення C # за допомогою попередньо обчисленої таблиці підрахунків бітів з розгалуженням на вхідний розмір.

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

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

Як не дивно, цю таблицю можна було створити будь-яким алгоритмом, розміщеним у цій темі! Тим не менш, використання таких таблиць означає продуктивність у постійному часі. Отже, якщо піти на крок далі та створити таблицю перекладу 64K, то вдвічі зменшиться операція AND, SHIFT та ADD. Цікава тема для бітових маніпуляторів!
user924272

Великі таблиці можуть бути повільнішими (а не постійним часом) через проблеми з кешем. Ви можете "шукати" 3 біти одночасно (0xe994 >>(k*2))&3, без доступу до пам'яті ...
greggo

5

Ось портативний модуль (ANSI-C), який може орієнтувати кожен ваш алгоритм у будь-якій архітектурі.

У вашому процесорі є 9 бітних байтів? Немає проблем :-) На даний момент він реалізує 2 алгоритми, алгоритм K&R та таблицю пошуку мудрених байтів. Таблиця пошуку в середньому в 3 рази швидша, ніж алгоритм K&R. Якщо хтось може зрозуміти спосіб зробити портативний алгоритм "Хакерське захоплення", сміливо додайте його.

#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), коли встановлюється кожен біт.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.