8 бит, представляющих число 7, выглядят следующим образом:
00000111
Три бита установлены.
Что такое алгоритмы для определения количества заданных битов в 32-битовом целое?
8 бит, представляющих число 7, выглядят следующим образом:
00000111
Три бита установлены.
Что такое алгоритмы для определения количества заданных битов в 32-битовом целое?
Это называется "" Хэмминг-вес "," popcount "или" боковое добавление".
"Лучший" алгоритм действительно зависит от того, на каком процессоре вы находитесь и на каком шаблоне использования.
Некоторые CPU имеют одну встроенную инструкцию, а другие - параллельные инструкции, которые действуют на битовые векторы. Параллельные инструкции (например, x86 popcnt
, на процессорах, где он поддерживается), почти наверняка будут самыми быстрыми. Некоторые другие архитектуры могут иметь медленную инструкцию, реализованную с микрокодированным циклом, который проверяет бит за цикл (цитата необходима).
Метод заполнения таблицы с заполненной таблицей может быть очень быстрым, если ваш процессор имеет большой кеш и/или вы выполняете множество этих инструкций в узком цикле. Однако он может пострадать из-за расхода "промаха в кеше", когда ЦП должен извлечь часть таблицы из основной памяти.
Если вы знаете, что ваши байты будут в основном 0 или в основном 1, тогда для этих сценариев есть очень эффективные алгоритмы.
Я считаю, что очень хорошим алгоритмом общего назначения является следующий, известный как "параллельный "или" алгоритм SWAR с переменной точностью". Я выразил это на псевдо-языке C-типа, вам может потребоваться настроить его для работы на определенном языке (например, используя uint32_t для С++ и → > в Java):
int numberOfSetBits(int i)
{
// Java: 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;
}
Это наилучшее худшее поведение любого из обсуждаемых алгоритмов, поэтому будет эффективно работать с любым шаблоном использования или значениями, которые вы бросаете на него.
Этот алгоритм с побайтовым SWAR может распараллеливаться в нескольких векторных элементах одновременно, а не в одном целочисленном регистре, для ускорения работы с CPU с SIMD, но без использования команды popcount. (например, код x86-64, который должен запускаться на любом процессоре, а не только в Nehalem или позже.)
Однако наилучшим способом использования векторных инструкций для popcount обычно является использование переменной-shuffle для выполнения поиска по таблице для 4 бит в момент каждого байта параллельно. (4-битный индекс содержит 16 записей, хранящихся в векторном регистре).
В процессорах Intel аппаратная 64-битная команда popcnt может превосходить SSSE3 PSHUFB
бит-параллельную реализацию примерно в 2 раза, но только если ваш компилятор получает это как раз правильно. В противном случае SSE может выйти значительно вперед. Более новые версии компилятора знают о ложной зависимости popcnt проблема в Intel.
Литература:
https://graphics.stanford.edu/~seander/bithacks.html
https://en.wikipedia.org/wiki/Hamming_weight
http://gurmeet.net/puzzles/fast-bit-counting-routines/
http://aggregate.ee.engr.uky.edu/MAGIC/#Population%20Count%20(Ones%20Count)
Также рассмотрите встроенные функции ваших компиляторов.
В компиляторе GNU, например, вы можете просто использовать:
int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);
В худшем случае компилятор будет генерировать вызов функции. В лучшем случае компилятор будет выдавать инструкцию cpu для выполнения той же самой работы быстрее.
Внутренние среды GCC работают даже на нескольких платформах. Popcount станет основной темой в архитектуре x86, поэтому имеет смысл начать использовать внутреннее значение. Другие архитектуры имеют много лет.
На x86 вы можете сообщить компилятору, что он может принять поддержку инструкции popcnt
с помощью -mpopcnt
или -msse4.2
, чтобы также включить векторные инструкции, которые были добавлены в том же поколении. См. Параметры GCC x86. -march=nehalem
(или -march=
любой процессор, который вы хотите, чтобы ваш код принимал и настраивал) мог бы быть хорошим выбором. Запуск полученного двоичного файла на более старом процессоре приведет к ошибке с неправильной инструкцией.
Чтобы сделать бинарные файлы оптимизированными для машины, на которой вы их построите, используйте -march=native
(с gcc, clang или ICC).
MSVC обеспечивает встроенную команду x86 popcnt
, но в отличие от gcc она действительно является неотъемлемой частью аппаратной инструкции и требует аппаратной поддержки.
Использование std::bitset<>::count()
вместо встроенного
Теоретически любой компилятор, который умеет эффективно собирать данные для целевого ЦП, должен раскрывать эту функциональность через ISO С++ std::bitset<>
. На практике вам может быть лучше с бит-взломом AND/shift/ADD в некоторых случаях для некоторых целевых ЦП.
Для целевых архитектур, где аппаратный popcount является дополнительным расширением (например, 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
испускает (для версии arg int
):
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, например.
По моему мнению, "лучшее" решение - это то, которое можно прочитать другим программистом (или оригинальным программистом два года спустя) без обильных комментариев. Вам может потребоваться самое быстрое или умное решение, которое некоторые уже предоставили, но в любое время я предпочитаю читаемость по поводу умения.
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);
}
Хотя они полагаются на конкретные типы данных, поэтому они не являются переносимыми. Но, поскольку многие оптимизации производительности в любом случае не переносятся, это может не быть проблемой. Если вы хотите переносить, я придерживаюсь читаемого решения.
От Hacker Delight, p. 66, Рисунок 5-2
int pop(unsigned x)
{
x = x - ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F;
x = x + (x >> 8);
x = x + (x >> 16);
return x & 0x0000003F;
}
Выполняется в ~ 20-их инструкциях (зависит от арки), без ветвления.
Хакерский восторг восхитителен! Очень рекомендуется.
Я думаю, что самый быстрый способ - без использования справочных таблиц и popcount - заключается в следующем. Он подсчитывает установленные биты всего за 12 операций.
int popcount(int v) {
v = v - ((v >> 1) & 0x55555555); // put count of each 2 bits into those 2 bits
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // put count of each 4 bits into those 4 bits
return c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}
Это работает, потому что вы можете подсчитать общее количество заданных бит, разделив их в две половины, подсчитав количество заданных бит в обеих половинах и затем добавив их. Также известно как парадигма Divide and Conquer
. Давайте подробно рассмотрим.
v = v - ((v >> 1) & 0x55555555);
Количество бит в двух битах может быть 0b00
, 0b01
или 0b10
. Давайте попробуем это разобрать на 2 бита.
---------------------------------------------
| v | (v >> 1) & 0b0101 | v - x |
---------------------------------------------
0b00 0b00 0b00
0b01 0b00 0b01
0b10 0b01 0b01
0b11 0b01 0b10
Это то, что было необходимо: последний столбец показывает количество установленных бит в каждой битовой паре. Если два битовых номера >= 2 (0b10)
, то and
создает 0b01
, иначе он создает 0b00
.
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
Это утверждение должно быть легко понятным. После первой операции у нас есть счетчик битов в каждом бите, теперь мы суммируем этот счет в каждых 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
.
Если вы используете Java, это сделает встроенный метод Integer.bitCount
.
Мне стало скучно и приурочено к миллиарду итераций трех подходов. Компилятор - gcc-O3. CPU - это то, что они вносят в 1-й ген MacBook Pro.
Самый быстрый из них: 3,7 секунды:
static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}
Второе место относится к одному и тому же коду, но ищет 4 байта вместо 2 полуслов. Это заняло около 5,5 секунд.
Третье место относится к подходу с боковым смещением, который занял 8,6 секунды.
Четвертое место переходит в GCC __builtin_popcount(), постыдное 11 секунд.
Подсчет с использованием метода "один бит в секунду" был медленным, и мне стало скучно ждать его завершения.
Итак, если вы заботитесь о производительности выше всех остальных, используйте первый подход. Если вам все равно, но недостаточно, чтобы потратить на нее 64 КБ ОЗУ, используйте второй подход. В противном случае используйте читаемый (но медленный) однобитовый подход.
Трудно подумать о ситуации, когда вы хотите использовать подход, основанный на бит.
Изменить: похожие результаты здесь.
unsigned int count_bit(unsigned int x)
{
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >> 16)& 0x0000FFFF);
return x;
}
Позвольте мне объяснить этот алгоритм.
Этот алгоритм основан на алгоритме Divide и Conquer. Предположим, что существует 8-битное целое число 213 (11010101 в двоичном виде), алгоритм работает так (каждый раз слияние двух соседних блоков):
+-------------------------------+
| 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | <- x
| 1 0 | 0 1 | 0 1 | 0 1 | <- first time merge
| 0 0 1 1 | 0 0 1 0 | <- second time merge
| 0 0 0 0 0 1 0 1 | <- third time ( answer = 00000101 = 5)
+-------------------------------+
Это один из тех вопросов, когда он помогает узнать вашу микроархитектуру. Я просто приурочил два варианта под gcc 4.3.3, скомпилированный с -O3, используя С++ inline, чтобы устранить накладные расходы на вызовы функций, один миллиард итераций, сохраняя текущую сумму всех счетчиков, чтобы гарантировать, что компилятор не удаляет ничего важного, используя rdtsc для синхронизации ( тактовый цикл).
inline int pop2(unsigned x, unsigned 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); return (x+y) & 0x000000FF; }
Неизмененный хакерский восторг занял 12,2 гигацикла. Моя параллельная версия (считая в два раза больше бит) работает в 13.0 gigacycles. Всего 10,5 с обоих вместе взятых на Core Duo 2,4 ГГц. 25 gigacycles = чуть более 10 секунд на этой тактовой частоте, поэтому я уверен, что мои тайминги правильные.
Это связано с цепочками зависимостей команд, которые очень плохо для этого алгоритма. Я мог бы удвоить скорость снова, используя пару 64-битных регистров. На самом деле, если бы я был умным и добавил x + y немного раньше, я мог бы сбрить некоторые смены. 64-битная версия с некоторыми небольшими настройками выйдет примерно ровно, но пересчитайте в два раза больше бит.
С 128-битными SIMD-регистрами, еще одним фактором из двух, и наборы команд SSE часто также имеют умные сокращения.
Нет причин, чтобы код был особенно прозрачным. Интерфейс прост, алгоритм можно ссылаться в режиме онлайн во многих местах, и он поддается всеобъемлющему unit test. Программист, который натыкается на него, может даже что-то узнать. Эти битовые операции чрезвычайно естественны на уровне машины.
ОК, я решил провести скачущую 64-битную версию. Для этого один sizeof (unsigned long) == 8
inline int pop2(unsigned long x, unsigned long y) { x = x - ((x >> 1) & 0x5555555555555555); y = y - ((y >> 1) & 0x5555555555555555); x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333); x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F; y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F; x = x + y; x = x + (x >> 8); x = x + (x >> 16); x = x + (x >> 32); return x & 0xFF; }
Это выглядит правильно (я не очень тщательно тестирую). Теперь тайминга выходят на 10,70 гигацикла /14,1 гигацикла. Это более позднее число суммировало 128 миллиардов бит и соответствует 5.9s, прошедшим на этой машине. Непараллельная версия ускоряет крошечный бит, потому что я работаю в 64-битном режиме, и он любит 64-битные регистры немного лучше, чем 32-разрядные регистры.
Посмотрим, есть ли здесь еще несколько конвейеров ООО. Это было немного более активно, поэтому я фактически немного протестировал. Каждый член сам по себе суммируется до 64, вся объединенная сумма равна 256.
inline int pop4(unsigned long x, unsigned long y, unsigned long u, unsigned long v) { enum { m1 = 0x5555555555555555, m2 = 0x3333333333333333, m3 = 0x0F0F0F0F0F0F0F0F, m4 = 0x000000FF000000FF }; x = x - ((x >> 1) & m1); y = y - ((y >> 1) & m1); u = u - ((u >> 1) & m1); v = v - ((v >> 1) & m1); x = (x & m2) + ((x >> 2) & m2); y = (y & m2) + ((y >> 2) & m2); u = (u & m2) + ((u >> 2) & m2); v = (v & m2) + ((v >> 2) & m2); x = x + y; u = u + v; x = (x & m3) + ((x >> 4) & m3); u = (u & m3) + ((u >> 4) & m3); x = x + u; x = x + (x >> 8); x = x + (x >> 16); x = x & m4; x = x + (x >> 32); return x & 0x000001FF; }
Я был взволнован на мгновение, но оказалось, что gcc играет встроенные трюки с -O3, хотя я не использую ключевое слово inline в некоторых тестах. Когда я позволяю gcc играть трюки, миллиард звонков в pop4() принимает 12.56 гигациклов, но я решил, что это аргументы сгибания как постоянные выражения. Более реалистичное число, по-видимому, составляет 19.6gc для еще 30% ускорения. Мой тестовый цикл теперь выглядит так, чтобы каждый аргумент был достаточно разным, чтобы остановить gcc от трюков.
hitime b4 = rdtsc(); for (unsigned long i = 10L * 1000*1000*1000; i < 11L * 1000*1000*1000; ++i) sum += pop4 (i, i^1, ~i, i|1); hitime e4 = rdtsc();
256 миллиардов бит, суммированных в 8.17s, истекли. Работает до 1.02s для 32 миллионов бит, сравнивая результаты поиска в 16 бит. Невозможно сравнивать напрямую, потому что другая скамейка не дает тактовой частоты, но выглядит так, как будто я ударил сопли из таблицы на 64 КБ, что является трагическим использованием кеша L1 в первую очередь.
Обновление: решили сделать очевидное и создать pop6(), добавив еще четыре дублированные строки. Вышло до 22,8 гц, и 384 млрд. Бит суммировано в 9,5 с. Так что еще 20% сейчас на 800 мс за 32 миллиарда бит.
Почему не итеративно делить на 2?
count = 0 while n > 0 if (n % 2) == 1 count += 1 n /= 2
Я согласен, что это не самый быстрый, но "лучший" несколько неоднозначен. Я бы сказал, что "лучший" должен иметь элемент ясности
Переплетение битов Hacker 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;
}
Первый шаг добавляет четные биты к нечетным битам, создавая сумму битов в каждых двух. Другие шаги добавляют чанки высокого порядка к чанам низкого порядка, удваивая размер чанка до тех пор, пока мы не получим окончательный счет, занимающий все целое.
Для счастливой среды между таблицей поиска 2 32 и итерированием через каждый бит отдельно:
int bitcount(unsigned int num){
int count = 0;
static int nibblebits[] =
{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
for(; num != 0; num >>= 4)
count += nibblebits[num & 0x0f];
return count;
}
Это можно сделать в O(k)
, где k
- количество установленных битов.
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
Это не самое быстрое или лучшее решение, но я нашел тот же вопрос на моем пути, и я начал думать и думать. наконец, я понял, что это можно сделать так, если вы получите проблему с математической стороны и нарисуете график, тогда вы обнаружите, что это функция, которая имеет некоторую периодическую часть, а затем вы понимаете разницу между периодами... так здесь вы идете:
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);
}
}
Функция, которую вы ищете, часто называется "боковая сумма" или "подсчет количества" двоичного числа. Кнут обсуждает его в дофашике 1A, pp11-12 (хотя в томе 2, 4.6.3- (7) была краткая ссылка).
Локус classicus - статья Петра Вегнера "Техника подсчета в двоичном компьютере", из Связь ACM, том 3 (1960) Номер 5, стр. 322. Он дает два разных алгоритма: один оптимизирован для чисел, которые, как ожидается, будут "разрежены" (т.е. Имеют небольшое количество единиц) и один для противоположного случая.
Несколько открытых вопросов: -
мы можем модифицировать алгоритм для поддержки отрицательного числа следующим образом: -
count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
count += 1
n /= 2
return count
теперь, чтобы преодолеть вторую проблему, мы можем написать algo как: -
int bit_count(int num)
{
int count=0;
while(num)
{
num=(num)&(num-1);
count++;
}
return count;
}
для полной справки см.:
http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html
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;
}
Я использую приведенный ниже код, который более интуитивно понятен.
int countSetBits(int n) {
return !n ? 0 : 1 + countSetBits(n & (n-1));
}
Логика: n и (n-1) сбрасывает последний бит набора из n.
P.S: Я знаю, что это не O (1) решение, хотя и интересное решение.
Что вы подразумеваете под "Лучшим алгоритмом"? Укороченный код или голодный код? Ваш код выглядит очень элегантно и имеет постоянное время выполнения. Код также очень короткий.
Но если скорость является основным фактором, а не размером кода, я думаю, что следующее может быть быстрее:
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-разрядное значение может быть быстрее.
Я написал быстрый битконтактный макрос для машин 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
поэтому, если взять первый столбец (A) выше, сдвинуть его вправо 1 бит и вычесть его из AB, я получаю вывод (CD). Расширение до 3 бит аналогично; вы можете проверить его с помощью 8-строчной логической таблицы, как показано выше, если хотите.
если вы используете С++, другой вариант - использовать метапрограммирование шаблонов:
// 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 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 )
вы могли бы, конечно, расширить этот шаблон, чтобы использовать разные типы (даже для автоматического определения размера бит), но я сохранил его просто для ясности.
edit: забыл упомянуть, что это хорошо, потому что он должен работать в любом компиляторе С++, и он просто разворачивает ваш цикл для вас, если для подсчета бит используется постоянное значение (другими словами, Я уверен, что это самый быстрый общий метод, который вы найдете)
Я особенно люблю этот пример из файла состояния:
#define BITCOUNT(x) (((BX_(x)+(BX_(x)>>4)) & 0x0F0F0F0F) % 255) #define BX_(x) ((x) - (((x)>>1)&0x77777777) - (((x)>>2)&0x33333333) - (((x)>>3)&0x11111111))
Мне нравится, потому что это так красиво!
Java JDK1.5
Integer.bitCount(п);
где n - число, чье число должно подсчитываться.
проверьте также,
Integer.highestOneBit(n);
Integer.lowestOneBit(n);
Integer.numberOfLeadingZeros(n);
Integer.numberOfTrailingZeros(n);
//Beginning with the value 1, rotate left 16 times
n = 1;
for (int i = 0; i < 16; i++) {
n = Integer.rotateLeft(n, 1);
System.out.println(n);
}
Я нашел реализацию подсчета бит в массиве с использованием команды SIMD (SSSE3 и AVX2). Он имеет производительность в 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];
}
Я всегда использую это в Конкурентном программировании, и это легко писать и эффективно:
#include <bits/stdc++.h>
using namespace std;
int countOnes(int n) {
bitset<32> b(n);
return b.count();
}
Вот портативный модуль (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
Существует множество алгоритмов для подсчета установленных битов; но я думаю, что лучший из них самый быстрый! Вы можете увидеть подробную информацию на этой странице:
Я предлагаю следующее:
Счетные биты, установленные в 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.
Быстрое решение С# с использованием предварительно вычисленной таблицы байт-бит с разветвлением по размеру ввода.
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
};
}
32-битный или нет? Я только пришел с этим методом в Java после прочтения " взлома интервью по кодированию " в 4-м издании 5.5 (глава 5: Манипулирование битами). Если младший бит равен 1 приращение count
, то сдвиг вправо целое число.
public static int bitCount( int n){
int count = 0;
for (int i=n; i!=0; i = i >> 1){
count += i & 1;
}
return count;
}
Я думаю, что это более интуитивно, чем решения с константой 0x33333333, независимо от того, насколько они быстры. Это зависит от вашего определения "лучший алгоритм".