32ビット整数のセットビット数を数える方法は?


868

数値7を表す8ビットは次のようになります。

00000111

3ビットが設定されます。

32ビット整数の設定ビット数を決定するアルゴリズムは何ですか?


101
これはハミングウェイトBTWです。
Purfideas 2008

11
このための実際のアプリケーションは何ですか?(これは批判として
受け取ら

8
通信での単純なエラー検出として使用された、パリティビットの計算(ルックアップ)。
Dialecticus 2010

8
@Dialecticus、パリティビットの計算は、ハミング重みの計算より安価です
finnw

15
@spookyjon隣接行列として表されるグラフがあるとします。これは基本的にビットセットです。頂点のエッジの数を計算する場合、要約すると、ビットセットの1つの行のハミング重みを計算します。
fuz

回答:


850

これは、「ハミングウェイト」、「ポップカウント」、または「横方向加算」として知られています。

「最適な」アルゴリズムは、実際に使用しているCPUと使用パターンに依存します。

一部のCPUには、それを実行するための単一の組み込み命令があり、他のCPUには、ビットベクトルに作用する並列命令があります。並列命令(popcntサポートされているCPUでのx86など)は、ほぼ確実に最速になります。他の一部のアーキテクチャでは、サイクルごとにビットをテストするマイクロコード化ループで実装された低速の命令がある場合があります(引用が必要)。

CPUに大きなキャッシュがある場合や、タイトなループでこれらの命令を大量に実行している場合は、事前入力されたテーブルルックアップメソッドは非常に高速です。ただし、CPUがメインメモリからテーブルの一部をフェッチしなければならない「キャッシュミス」が発生するため、問題が発生する可能性があります。(テーブルを小さく保つために、各バイトを個別に調べてください。)

バイトがほとんど0または1になることがわかっている場合、これらのシナリオには非常に効率的なアルゴリズムがあります。

非常に優れた汎用アルゴリズムは、「並列」または「可変精度SWARアルゴリズム」として知られている次のアルゴリズムであると思います。これをCのような疑似言語で表現しましたが、特定の言語で動作するように調整する必要がある場合があります(たとえば、C ++でuint32_tを使用し、Javaで>>>を使用)。

int numberOfSetBits(uint32_t i)
{
     // Java: use int, and use >>> instead of >>
     // C or C++: use uint32_t
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

JavaScriptの場合:整数に強制変換して|0、パフォーマンスのために:への最初の行を変更i = (i|0) - ((i >> 1) & 0x55555555);

これは、説明したアルゴリズムの中で最もワーストケースの振る舞いがあり、使用するパターンや値を効率的に処理します。


このSWARビットハックの仕組み:

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

最初のステップは、奇数/偶数ビットを分離し、それらを整列させるためにシフトし、追加するためのマスキングの最適化バージョンです。これにより、2ビットアキュムレータで16の個別の加算が効果的に行われます(SWAR = A Register Within A Register)。のように(i & 0x55555555) + ((i>>1) & 0x55555555)

次のステップでは、これらの16x 2ビットアキュムレータの奇数/偶数8を取り、再び加算して、8x 4ビットの合計を生成します。今回はi - ...最適化は不可能なので、シフトの前/後にマスクするだけです。シフト0x33...0xccc...前ではなく両方で同じ定数を使用することは、32ビット定数をレジスターに個別に構成する必要があるISAをコンパイルする場合に適しています。

(i + (i >> 4)) & 0x0F0F0F0F4x 8ビットアキュムレータに拡張する最後のシフトおよび追加ステップ。対応する入力ビットの4ビットすべてが設定されている場合、4ビットアキュムレータの最大値はであるため、前ではなく追加にマスクし4ます。4 + 4 = 8は4ビットに収まるため、ニブル要素間のキャリーはで不可能ですi + (i >> 4)

これまでのところ、これは、いくつかの巧妙な最適化を伴うSWAR技術を使用した、ごく普通のSIMDです。同じパターンをさらに2ステップ続けると、16ビットのカウントが2倍になり、次に32ビットのカウントが1倍になります。しかし、高速なハードウェア乗算を備えたマシンでは、より効率的な方法があります。

十分な数の「要素」が得られたら、魔法の定数で乗算すると、すべての要素を合計して最上位の要素にすることができます。この場合はバイト要素です。乗算は左シフトと加算によって行われるため、乗算x * 0x01010101結果はになりx + (x<<8) + (x<<16) + (x<<24)ます。 私たちの8ビット要素は十分に広い(そして十分に小さいカウントを保持している)ため、これによって上位8ビットにキャリー発生しません。

これの64ビットバージョンは、64ビット整数で8x 8ビットエレメントを0x0101010101010101乗数で実行し、で上位バイトを抽出できます>>56。したがって、追加の手順は必要なく、より広い定数のみが使用されます。これは__builtin_popcountll、ハードウェアpopcnt命令が有効になっていないときにx86システムでGCCが使用するものです。これにビルトインまたは組み込みを使用できる場合は、ターゲット固有の最適化を行う機会をコンパイラーに与えるために使用してください。


より広いベクトル用の完全なSIMDを使用(例:配列全体をカウント)

このビット単位のSWARアルゴリズムは、SIMDを使用するが使用可能なpopcount命令を持たないCPUでスピードアップするために、単一の整数レジスターではなく、一度に複数のベクトル要素で並列化できます。(たとえば、Nehalem以降だけでなく、任意のCPUで実行する必要があるx86-64コード。)

ただし、popcountのベクトル命令を使用する最良の方法は、通常、可変シャッフルを使用して、各バイトの並列で一度に4ビットのテーブルルックアップを実行することです。(4ビットは、ベクトルレジスタに保持される16エントリテーブルのインデックスです)。

Intel CPUでは、ハードウェア64ビットpopcnt命令は、SSSE3 PSHUFBビット並列実装よりも約2倍優れていますが、コンパイラーが適切に処理した場合のみです。そうしないと、SSEが大幅に先行する可能性があります。新しいコンパイラー・バージョンは、インテルでのpopcnt false依存関係の 問題を認識しています。

参照:


87
ハ!NumberOfSetBits()関数が大好きですが、コードレビューでそれを取得してください。:-)
Jason S

37
たぶん、を使用してunsigned int、符号ビットの複雑化がないことを簡単に示す必要があります。またuint32_t、すべてのプラットフォームで期待どおりの結果が得られるように、より安全でしょうか?
クレイグマックイーン

35
@nonnb:実際には、書かれているように、コードにはバグがあり、メンテナンスが必要です。>>負の値に対して実装定義されています。引数をに変更(またはキャスト)する必要がありunsigned、コードは32ビット固有であるため、おそらくを使用する必要がありますuint32_t
R .. GitHub STOP HELPING ICE

6
それは本当に魔法ではありません。それはビットのセットを追加していますが、いくつかの巧妙な最適化で追加しています。回答に記載されているウィキペディアのリンクは、何が起こっているのかを説明するのに役立ちますが、1行ずつ説明します。1)すべてのビットのペアのビット数をカウントアップし、そのカウントをそのビットのペアに入れます(00、01、または10になります)。ここの「賢い」ビットは、1つのマスクを回避する減算です。2)それらのビットペアの合計のペアを対応するニブルに追加します。ここでは賢いことはありませんが、各ニブルの値は0〜4になります。(続き)
dash-tom-bang

8
別の注意点として、これは定数を適切に拡張するだけで64ビットと128ビットのレジスタに拡張されます。興味深いことに(私にとって)、これらの定数も〜0 / 3、5、17、および255です。前の3つは2 ^ n + 1です。これは、あなたがそれをじっと見つめ、シャワーでそれを考えるほど、より理にかなっています。:)
dash-tom-bang

214

コンパイラの組み込み関数も考慮してください。

たとえば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=、コードが想定して調整するCPUはどれでも)良い選択です。結果のバイナリを古いCPUで実行すると、不正な命令エラーが発生します。

バイナリをビルドするマシン用に最適化するには、-march=native (gcc、clang、またはICCを使用)を使用します。

MSVCはx86 popcnt命令の組み込み関数を提供しますが、gccとは異なり、ハードウェア命令の組み込み関数であり、ハードウェアサポートが必要です。


std::bitset<>::count()組み込みの代わりに使用

理論的には、ターゲットCPUに対して効率的にポップカウントする方法を知っているコンパイラは、ISO C ++を通じてその機能を公開する必要がありますstd::bitset<>。実際には、ターゲットCPUによっては、ビットハック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();
}

Godboltコンパイラエクスプローラで、gcc、clang、icc、およびMSVCからのasmを参照してください。

x86-64 gcc -O3 -std=gnu++11 -mpopcntはこれを発行します:

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

PowerPC64が発行しますgcc -O3 -std=gnu++11intargバージョンの場合):

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

このソースは、x86固有でもGNU固有でもありませんが、gcc / clang / iccを使用してx86用に適切にコンパイルするだけです。

また、単一命令のpopcountを持たないアーキテクチャに対するgccのフォールバックは、一度に1バイトずつのテーブルルックアップです。これは、たとえばARMにとって素晴らしいことはありません。


5
これは一般的に良い習慣であることに同意しますが、XCode / OSX / Intelでは、ここに掲載されているほとんどの提案よりも遅いコードを生成することがわかりました。詳細については、私の回答を参照してください。

5
Intel i5 / i7には、汎用レジスターを使用してそれを行うSSE4命令POPCNTがあります。私のシステムのGCCは、この組み込み関数を使用してその命令を発行しません。これは、-march = nehalemオプションがまだないためです。
matja 2009年

3
@matja、私のGCC 4.4.1は-msse4.2でコンパイルするとpopcnt命令を発行します
Nils Pipenbrinck 09年

74
c ++を使用しますstd::bitset::count。インライン化した後、これは単一の__builtin_popcount呼び出しにコンパイルされます。
deft_code

1
@nlucaroniそうですね。時代は変化しています。この回答は2008年に書きました。現在、ネイティブポップカウントがあり、プラットフォームで許可されている場合、組み込み関数は1つのアセンブラステートメントにコンパイルされます。
Nils Pipenbrinck 2013

184

私の意見では、「最良の」ソリューションは、別のプログラマー(または2年後の元のプログラマー)が大量のコメントなしで読むことができるものです。一部のユーザーがすでに提供している最速または賢いソリューションが必要な場合もありますが、私はいつでも賢さよりも読みやすさを優先しています。

unsigned int bitCount (unsigned int value) {
    unsigned int count = 0;
    while (value > 0) {           // until all bits are zero
        if ((value & 1) == 1)     // check lower bit
            count++;
        value >>= 1;              // shift bits, removing lower bit
    }
    return count;
}

より高速にしたい場合(そして、後継者を助けるためにそれをうまく文書化すると仮定した場合)、テーブルルックアップを使用できます。

// Lookup table for fast calculation of bits set in 8-bit unsigned char.

static unsigned char oneBitsInUChar[] = {
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F (<- n)
//  =====================================================
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, // 0n
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, // 1n
    : : :
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, // Fn
};

// Function for fast calculation of bits set in 16-bit unsigned short.

unsigned char oneBitsInUShort (unsigned short x) {
    return oneBitsInUChar [x >>    8]
         + oneBitsInUChar [x &  0xff];
}

// Function for fast calculation of bits set in 32-bit unsigned int.

unsigned char oneBitsInUInt (unsigned int x) {
    return oneBitsInUShort (x >>     16)
         + oneBitsInUShort (x &  0xffff);
}

これらは特定のデータ型サイズに依存しているため、それほど移植性がありません。ただし、パフォーマンスの最適化の多くはとにかく移植性がないため、それは問題ではない可能性があります。移植性が必要な場合は、読みやすいソリューションを使用します。


21
2で割って「シフトビット...」とコメントするのではなく、シフト演算子(>>)を使用してコメントを省略します。
2008

9
置き換える方が理にかなっているif ((value & 1) == 1) { count++; }count += value & 1思いませんか?
Ponkadoodle 2010

21
いいえ、この場合、最適なソリューションは最も読みやすいものではありません。ここで最良のアルゴリズムは最速のものです。
NikiC 2010

21
@nikic、それは完全にあなたの意見ですが、明らかに私に反対投票することは自由です。「最良」をどのように定量化するかについての質問には、言及がありませんでした。「パフォーマンス」または「高速」という言葉はどこにも見られません。そのため、私は読み取り可能を選択しました。
paxdiablo

3
私は3年後にこの回答を読んでいますが、読みやすく、コメント数が多いため、これが最良の回答であると思います。限目。
waka-waka-waka

98

ハッカーの喜びから、p。66、図5-2

int pop(unsigned x)
{
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

〜20-ish命令(archに依存)で実行し、分岐しません。

ハッカーの喜び 楽しいです!強くお勧めします。


8
JavaメソッドInteger.bitCount(int)は、これとまったく同じ実装を使用します。
Marco Bolis、2015年

これに少し問題があります-32ビットではなく、16ビットの値だけを気にするとどう変化しますか?
Jeremy Blum

多分ハッカーの喜びは楽しいですが、私はこれpopを呼び出す代わりにpopulation_count(またはpop_cnt省略が必要な場合)誰かに良い蹴りを与えます。@MarcoBolis Javaのすべてのバージョンに当てはまると思いますが、正式には実装に依存します:)
Maarten Bodewes

そして、これは受け入れられた回答のコードのように乗算を必要としません。
Alex

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;
}

これは、2つの半分に分割して両方の半分のセットビット数をカウントし、それらを加算することで、セットビットの総数をカウントできるため機能します。Divide and Conquerパラダイムとしても知られています。詳細に入りましょう。

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

2ビットのビット数は0b000b01または0b10です。これを2ビットで解決してみましょう。

 ---------------------------------------------
 |   v    |   (v >> 1) & 0b0101   |  v - x   |
 ---------------------------------------------
   0b00           0b00               0b00   
   0b01           0b00               0b01     
   0b10           0b01               0b01
   0b11           0b01               0b10

これが必要なものです。最後の列は、2ビットペアごとのセットビットの数を示しています。2ビット数が生成される>= 2 (0b10)場合and0b01、それ以外の場合は生成され0b00ます。

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

このステートメントは理解しやすいはずです。最初の操作の後、2ビットごとにセットされたビットの数がわかりました。次に、4ビットごとにその数を合計します。

v & 0b00110011         //masks out even two bits
(v >> 2) & 0b00110011  // masks out odd two bits

次に、上記の結果を合計し、設定されたビットの合計数を4ビットで示します。最後のステートメントは最もトリッキーです。

c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

さらに分解してみましょう...

v + (v >> 4)

2番目のステートメントに似ています。代わりに、4のグループでセットビットをカウントしています。以前の操作により、すべてのニブルにセットビットのカウントが含まれていることがわかります。例を見てみましょう。バイトがあるとします0b01000010。これは、最初のニブルに4ビットセットがあり、2番目のニブルに2ビットセットがあることを意味します。次に、これらのニブルを一緒に追加します。

0b01000010 + 0b01000000

これは、最初のニブルの1バイトのセットビット数を示し0b01100010ます。したがって、数値のすべてのバイトの最後の4バイトをマスクします(破棄します)。

0b01100010 & 0xF0 = 0b01100000

これで、すべてのバイトにセットビットのカウントが含まれます。それらをすべて合計する必要があります。秘訣は0b10101010、興味深い特性を持つ結果を乗算することです。私たちの数が4バイトの場合A B C D、これらのバイトで新しい数になりますA+B+C+D B+C+D C+D D。4バイトの数値には、最大32ビットを設定できます0b00100000。これは、として表すことができます。

ここで必要なのは、すべてのバイトのすべての設定ビットの合計を含む最初のバイトだけ >> 24です。このアルゴリズムは32 bit単語用に設計されていますが、単語用に簡単に変更でき64 bitます。


c = についてですか?排除する必要があるように見えます。さらに、いくつかの古典的な警告を回避するために、追加の括弧セットA "((((v +(v >> 4))&0xF0F0F0F)* 0x1010101)>> 24"を提案します。
chux-モニカを2013年

4
重要な機能は、この32ビットルーチンがとの両方popcount(int v)で機能することpopcount(unsigned v)です。移植性についてはpopcount(uint32_t v)、などを考慮してください。* 0x1010101パーツと同じように。
chux-モニカを2013年

ソース ?(本、リンク、招待者の名前など)は大歓迎です。それから、それをコードベースにコメントとともに貼り付けることができるからです。
v.oddou 2015年

1
より明確にするために、最後の行は次のように書くべきだと思います:return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;したがって、実際に何をしているのかを確認するために文字を数える必要はありません(最初のを破棄した0ため、誤った(反転した)ビットパターンをマスクとして誤って使用したと思いました-それは、私が8文字ではなく、7文字しかないことに気づくまでです。
emem

その乗算 0x01010101によっては、プロセッサに依存し、遅いかもしれません。たとえば、私の古いPowerBook G4では、1乗算は約4加算と遅くなりました(1除算は約23加算と遅く、除算ほど悪くありませんでした)。
ジョージケーラー2018年

54

私は退屈し、3つのアプローチを10億回繰り返した。コンパイラはgcc -O3です。CPUは、第1世代Macbook Proに搭載されているものです。

最速は次の3.7秒です。

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

2位は同じコードですが、2ハーフワードではなく4バイトを検索します。約5.5秒かかりました。

3位は8.6秒かかった、ちょっとした「横向き加算」アプローチです。

4位は恥ずべき11秒でGCCの__builtin_popcount()に行きます。

一度に1ビットずつカウントするアプローチは遅くなり、完了するのを待つのに飽き飽きしました。

したがって、何よりもパフォーマンスを重視する場合は、最初のアプローチを使用してください。気にしても64KbのRAMを使うほどではない場合は、2番目の方法を使用します。それ以外の場合は、読み取り可能な(ただし遅い)一度に1ビットずつのアプローチを使用します。

ビットをいじるアプローチを使用したい状況を考えるのは難しいです。

編集:ここで同様の結果。


49
@マイク、テーブルがキャッシュにある場合、テーブルベースのアプローチは無敵です。これはマイクロベンチマークで発生します(タイトループで数百万のテストを実行するなど)。ただし、キャッシュミスは約200サイクルかかり、最も単純なポップカウントでさえここでは高速になります。それは常にアプリケーションに依存します。
Nils Pipenbrinck 2008

10
このルーチンをタイトなループで数百万回呼び出さない場合は、パフォーマンスをまったく気にする必要はありません。パフォーマンスの低下は無視できるので、単純だが読み取り可能な方法を使用することもできます。FWIW、8ビットLUTは10〜20回の呼び出しでキャッシュホットになります。

6
これがメソッドから行われたリーフコールである状況を想像するのはそれほど難しくないと思います。他に何が起こっているか(およびスレッド化)に応じて、小さいバージョンが勝つ可能性があります。参照の局所性が向上したため、他のアルゴリズムを凌ぐアルゴリズムが多数作成されています。これもどうですか?
ジェイソン

これをclangで試してみてください。組み込み関数の実装が大幅に向上しています。
マットジョイナー

3
-msse4.2で呼び出されない限り、GCCはpopcont命令を発行しません。
lvella

54

たまたまJavaを使用している場合は、組み込みメソッドInteger.bitCountがそれを行います。


sunが異なるAPIを提供した場合、バックグラウンドで何らかのロジックを使用している必要がありますよね?
Vallabh Patade

2
ちなみに、Javaの実装では、Kevin Littleが指摘したのと同じアルゴリズムを使用しています。
Marco Bolis、2015年

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)があるとすると、アルゴリズムは次のように機能します(毎回2つの隣接ブロックをマージします)。

+-------------------------------+
| 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 2013

29

これは、マイクロアーキテクチャを理解するのに役立つ質問の1つです。C ++インラインを使用して-O3でコンパイルされたgcc 4.3.3の下で2つのバリアントのタイミングを計ったところ、関数呼び出しのオーバーヘッド、10億回の反復を排除し、すべてのカウントの実行合計を維持して、タイミングにrdtscを使用して、コンパイラーが重要なものを削除しないようにしました(正確なクロックサイクル)。

インラインint pop2(符号なしx、符号なしy)
{
    x = x-((x >> 1)&0x55555555);
    y = y-((y >> 1)&0x55555555);
    x =(x&0x33333333)+((x >> 2)&0x33333333);
    y =(y&0x33333333)+((y >> 2)&0x33333333);
    x =(x +(x >> 4))&0x0F0F0F0F;
    y =(y +(y >> 4))&0x0F0F0F0F;
    x = x +(x >> 8);
    y = y +(y >> 8);
    x = x +(x >> 16);
    y = y +(y >> 16);
    return(x + y)&0x000000FF;
}

変更されていないHacker's Delightは12.2ギガサイクルかかりました。私の並列バージョン(2倍のビットを数える)は13.0ギガサイクルで動作します。2.4 GHz Core Duoでは、両方で合計10.5秒が経過しました。25ギガサイクル=このクロック周波数で10秒強なので、タイミングが正しいと確信しています。

これは、このアルゴリズムにとって非常に悪い命令依存チェーンに関係しています。64ビットレジスタのペアを使用すると、速度をほぼ2倍にすることができます。実際、私が賢く、少し早くx + yaを追加した場合は、シフトをいくつか取り除くことができました。多少の微調整を加えた64ビットバージョンはほぼ均等になりますが、再び2倍のビットを数えます。

128ビットのSIMDレジスタを使用すると、さらに2の因数があり、SSE命令セットには、多くの場合、巧妙なショートカットもあります。

コードが特に透過的である理由はありません。インターフェースはシンプルで、アルゴリズムは多くの場所でオンラインで参照でき、包括的な単体テストに適しています。それにつまずくプログラマーは何かを学ぶかもしれません。これらのビット操作は、マシンレベルでは非常に自然です。

OK、私は調整された64ビットバージョンのベンチマークを行うことにしました。この1つの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); 
    xと0xFFを返す;
}

それはほぼ問題ありません(ただし、慎重にテストしていません)。現在、タイミングは10.70ギガサイクル/ 14.1ギガサイクルで出ています。その後の数は1,280億ビットを合計し、このマシンで経過した5.9秒に対応します。非パラレルバージョンは、64ビットモードで実行しており、64ビットレジスタが32ビットレジスタよりもわずかに優れているため、非常に高速です。

ここにOOOパイプラインがもう少しあるかどうか見てみましょう。これはもう少し複雑だったので、実際に少しテストしました。各項だけを合計すると64になり、すべてを合計すると256になります。

inline int pop4(unsigned long x、unsigned long y、 
                unsigned long u、unsigned long v)
{
  列挙型{m1 = 0x5555555555555555、 
         m2 = 0x3333333333333333、 
         m3 = 0x0F0F0F0F0F0F0F0F、 
         m4 = 0x000000FF000000FF};

    x = x-((x >> 1)&m1);
    y = y-((y >> 1)&m1);
    u = u-((u >> 1)&m1);
    v = v-((v >> 1)&m1);
    x =(x&m2)+((x >> 2)&m2);
    y =(y&m2)+((y >> 2)&m2);
    u =(u&m2)+((u >> 2)&m2);
    v =(v&m2)+((v >> 2)&m2);
    x = x + y; 
    u = u + v; 
    x =(x&m3)+((x >> 4)&m3);
    u =(u&m3)+((u >> 4)&m3);
    x = x + u; 
    x = x +(x >> 8);
    x = x +(x >> 16);
    x = x&m4; 
    x = x +(x >> 32);
    xと0x000001FFを返します。
}

しばらく興奮しましたが、一部のテストでinlineキーワードを使用していなくても、gccが-O3を使用してインライントリックを実行していることがわかりました。gccにトリックをさせたところ、pop4()への10億回の呼び出しは12.56ギガサイクルかかりましたが、定数式としての引数の折りたたみであると判断しました。さらに現実的な数値は、さらに30%高速化するために19.6gcのようです。テストループは次のようになり、各引数が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(); 

8.17秒で合計された256億ビットが経過しました。16ビットテーブルルックアップでベンチマークされたように、3,200万ビットで1.02秒まで機能します。他のベンチはクロック速度を提供しないため、直接比較することはできませんが、そもそも64KBテーブルエディションから鼻水を抜いたように見えます。これはそもそもL1キャッシュの悲劇的な使用です。

更新:明白なことを行い、重複する4行を追加してpop6()を作成することにしました。22.8gcに達し、9.5秒で合計された3,840億ビットが経過しました。したがって、32億ビットで800msに20%が追加されます。


2
このような最高の非アセンブラー形式は、一度に24個の32ビットワードを展開したものです。dalkescientific.com/writings/diary/popcnt.cstackoverflow.com/questions/3693981/...dalkescientific.com/writings/diary/archive/2008/07/05/...
マット・ジョイナー

28

繰り返し2で除算しないのはなぜですか?

カウント= 0
n> 0の間
  if(n%2)== 1
    カウント+ = 1
  n / = 2  

これは最速ではないことに同意しますが、「最良」はややあいまいです。「ベスト」には明快さの要素があるべきだと私は主張します


それはうまくいき理解しやすいですが、より速い方法があります。
マットハウエルズ

2
これをLOTにしない限り、パフォーマンスへの影響はごくわずかです。だから、すべてが等しいので、「最高」は「意味不明なように読まない」ことを意味するダニエルに同意します。

2
さまざまな方法を得るために、「最善」を意図的に定義しませんでした。この種のいじりのレベルに達した場合は、チンパンジーが入力したような超高速なものを探している可能性があります。
マットハウエルズ

6
悪いコード。コンパイラーはそれから良いものになるかもしれませんが、私のテストではGCCはそうではありませんでした。(n%2)を(n&1)に置き換えます。そしてMODULOよりもはるかに高速です。(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;
}

最初のステップでは、偶数ビットを奇数ビットに追加して、各2つのビットの合計を生成します。他のステップでは、高位のチャンクを低位のチャンクに追加し、最後のカウントがint全体を占めるまで、チャンクサイズを2倍にします。


3
この解決策には、演算子の優先順位に関連する小さな問題があるようです。各用語について、次のように記述する必要があります:x =(((x >> 1)&0b01010101010101010101010101010101)+(x&0b01010101010101010101010101010101)); (つまり、追加の括弧が追加されます)。
Nopik、2014

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がそこにあります...
Robert S. Barnes '29

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;
}

これは基本的にブライアンカーニハンの(彼を覚えていますか?)のアルゴリズムですが、マイナーな変更により、より簡潔な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
ああ、私はそれが好きです。どのように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

探している関数は、しばしば2進数の「横向きの合計」または「母集団カウント」と呼ばれます。Knuthは、Facsicle 1A以前のpp11-12でそれについて説明しています(ただし、Volume 2の簡単なリファレンスは4.6.3-(7)です)。

軌跡classicusはからのピーターウェグナー氏の記事「バイナリコンピュータでカウントワンズのテクニック」、であるACMのコミュニケーション、第3巻(1960)ナンバー5、322ページ。そこで彼は2つの異なるアルゴリズムを提供します。1つは「スパース」であると予想される数(つまり、数が少ない)に最適化されたアルゴリズムと、その逆の場合のアルゴリズムです。


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

次に、2番目の問題を克服するために、次のようにアルゴリズムを記述します。

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ビットワードがある場合、ループを通過するのは1回だけです。

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版。(ブライアンW.カーニハンおよびデニスM.リッチーによる)演習2-9でこれについて言及しています。2006年4月19日、Don Knuthは、この方法は「Peter WegnerによってCACM 3(1960)、322で最初に公開されました。


8

私はより直感的な次のコードを使用します。

int countSetBits(int n) {
    return !n ? 0 : 1 + countSetBits(n & (n-1));
}

論理:n&(n-1)は、nの最後に設定されたビットをリセットします。

PS:興味深い解決策ではありますが、これはO(1)の解決策ではありません。


これは、ビット数が少ない「まばらな」数値に適していますO(ONE-BITS)。最大で32個の1ビットがあるため、これは実際にO(1)です。
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 2008

実は、このアプローチは、ビットをいじるアプローチよりもかなり高速です。より多くのメモリを使用することに関して、それはより少ないコードにコンパイルされ、そのゲインは関数をインライン化するたびに繰り返されます。したがって、それは簡単に正味の勝利となる可能性があります。

7

私はRISCマシン用の高速ビットカウントマクロを1990年頃に作成しました。高度な演算(乗算、除算、%)、メモリフェッチ(遅すぎる)、分岐(遅すぎる)を使用していませんが、CPUに32ビットのバレルシフター(つまり、>> 1と>> 32は同じサイクル数を必要とします。)小さな定数(6、12、24など)は、レジスターにロードするのにコストがかからないか、または格納されると想定しています。一時的に、何度も何度も再利用されます。

これらの仮定では、ほとんどのRISCマシンで約16サイクル/命令で32ビットをカウントします。15の命令/サイクルは、加数の数を半分に削減するために少なくとも3つの命令(マスク、シフト、演算子)を要するため、log_2(32)であるため、サイクルまたは命令の数の下限に近いことに注意してください。 = 5、5 x 3 = 15命令は準下限です。

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

最初の最も複雑なステップの秘訣は次のとおりです。

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

したがって、上の1列目(A)を取り、それを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ん。
イマレット2015

同意しました-これはC ++テンプレートの再帰における楽しい練習でしたが、間違いなくかなり単純な解決策でした。
ペンタフォーブ2015

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 '07

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
いくつかのJava開発者が法を認識していない可能性がありますので@benzadoは、とにかく右ですが、1
finnw

@finnw、私はそれらの開発者の一人です。:)
neevek 2013年

6

SIMD命令(SSSE3およびAVX2)を使用した配列でのビットカウントの実装を見つけました。__popcnt64組み込み関数を使用する場合よりも2〜2.5倍優れたパフォーマンスを発揮します。

SSSE3バージョン:

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2バージョン:

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}

6

私は常にこれを競合プログラミングで使用しており、簡単に記述でき、効率的です。

#include <bits/stdc++.h>

using namespace std;

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

5

セットされたビットをカウントするための多くのアルゴリズムがあります。しかし、私は最高のものはより速いものだと思います!このページで詳細を見ることができます:

ビットをくすぐるハック

私はこれを提案します:

64ビット命令を使用して14、24、または32ビットワードで設定されたカウントビット

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

// option 1, for at most 14-bit values in v:
c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

// option 2, for at most 24-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
     % 0x1f;

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

この方法では、効率を上げるために高速モジュラス除算を備えた64ビットCPUが必要です。最初のオプションは3つの操作のみを必要とします。2番目のオプションは10です。3番目のオプションは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

キャッシュの問題により、テーブルが大きくなると(一定時間ではなく)遅くなる可能性があります。(0xe994 >>(k*2))&3メモリアクセスなしで、一度に3ビットを「検索」できます...
greggo

5

これは、任意のアーキテクチャで各アルゴリズムをベンチマークできるポータブルモジュール(ANSI-C)です。

CPUに9ビットバイトがありますか?問題ありません:-)現時点では、K&Rアルゴリズムとバイト単位のルックアップテーブルという2つのアルゴリズムを実装しています。ルックアップテーブルは、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の右端の設定ビットから反転されます。したがって、この2つを実行すると、すべての反復で右端のビットが0になり、常に次の右端のセットビットに移動します。したがって、セットビットをカウントします。すべてのビットが設定されている場合、最悪の時間の複雑さはO(logn)になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.