Daiji Blog

bitを高速に数える手法の比較

公開:
更新:

はじめに

本記事ではbit1の数を数える手法アルゴリズムの比較を行いますアルゴリズムの詳しい内容等は紹介せずあくまで処理速度の比較だけ行いますアルゴリズムの詳しい内容は下の参考文献に飛んでください

アルゴリズム

Hamming weightで紹介されている5つの手法の演算回数について簡単にまとめますAは有名なアルゴリズムをそのまま実装した状態ですBCAの高速化である派生ですDbitが立っている1になっている数に比例して演算量が増えますEは力技で16 bitすべての数に対して立ってるbitの数をメモしておき64 bit4分割してその合計として求めていますメモリを多く使用し64 bitのようにbit数が多い場合はあまり高速ではありません

手法算術乗算比較/分岐メモリ読み込み特徴
A24000単純な実装
B17000乗算が遅い環境で優秀
C12100乗算が速い環境で優秀
D3n01n0n1になっている数
E900416 bitごとに読み取る

実験

実験環境ソースコード

実験で使用したソースコードはGitHubにあげておきます実験環境は下のとおりです最適化についてはGitHubにあげているMakefileを見てください

実験1単純な比較

まずは全bitに対して処理を行う安直な手法N5つの手法A—EC言語に実装してタイムを計測します1億個の乱数に対してビットカウントを行いその処理時間を計測します計測結果を表にまとめますこのときの立っているbitの平均は32です64 bitの乱数でやりましたこの環境ではCBよりも高速でしたDはこの中ではもっとも遅いですがそれでもNに比べて十分高速ですECと同等の速度ですがメモリのことを踏まえると微妙に感じます

手法時間
N4.447 s
A0.541 s
B0.467 s
C0.386 s
D3.025 s
E0.393 s

実験2特殊環境下での比較

次に立っているbitの数が平均0.5の場合で測定しますD4倍程度高速になったがもっと高速化されてもいいのかなと思いましたしかしCなどと比較しまだ遅いことからこの環境下では使用する必要性はないと思いますまたNは下位bitから参照し0になったとき終了するようにしているため高速化されたがそもそもNは使わない

手法時間
N1.966 s
A0.534 s
B0.458 s
C0.381 s
D0.735 s
E0.364 s

実験3 -march=nativeによる最適化

-march=nativeは特定のCPUを指定して高速化を行いますコンパイル後のバイナリデータを他の人と共有する場合は不適切ですが自分の環境だけでの使用の場合はオススメします実験結果を示しますD以外は少し高速化されました注目できるのはD30倍程度高速化されこの中で最速となっています

手法時間
N4.257 s
A0.380 s
B0.235 s
C0.181 s
D0.091 s
E0.212 s

実験4 _popcnt64__asm__

最後に_popcnt64__asm__を利用して計測する-march=native無しの結果をN有りの結果をYに示しています結果はどの自作関数より高速になりましたこれらが使える環境では使用したほうが良いと思います

手法時間 (N)時間 (Y)
_popcnt640.234 s0.071 s
__asm__0.234 s0.071 s

まとめ

bitを高速に数える手法の比較を行いました安直に数える場合に比べて10倍程度高速に数えるアルゴリズムであると確認できました_popcnt64__asm__はこれらに比べてより高速であることも確認できました

文献

  1. ソースコード
  2. Hamming weight
  3. x86_64popcnt / tzcnt / lzcntするビット演算テクニック Advent Calendar 2016 5日目
  4. GCC
  5. bitを高速に数える 手法の比較