ビットベクトルの基礎と応用:表現・演算・圧縮・rank/selectを徹底解説
ビットベクトルとは
ビットベクトル(bit vector、bitset、bitmap)は、各要素が0か1の値をとる配列をビット単位で表現したデータ構造です。各ビットが集合の要素存在・不在、フラグ、特徴量の有無などの真偽情報を表現するため、メモリ効率が高く、高速なビット演算による並列処理(ワード並列性)を活かせる点が特徴です。
基本表現と内部実装
一般的には、ビットベクトルはCPUワード(32-bit/64-bit)を単位にした配列で保持されます。例えば64ビットワードを使う場合、Nビットのビットベクトルは ceil(N/64) 個の uint64_t 配列で表現されます。
- ビットの番号付け:要素 i がどのワードの何ビットに対応するかを計算する(word = i / W, bit = i % W)。LSB(最下位ビット)を0番とするか、MSBを0番とするかは実装による。
- 演算:AND, OR, XOR などは対応するワード同士のビット演算で一括処理できる。
- インデックス(最小/最大セットビット)の取得や個数カウント(popcount)は専用命令やビルトイン関数で高速化できる。
主要操作と低レベル最適化
代表的な操作とその高速化テクニックは次の通りです。
- 集合演算(AND/OR/XOR/NOT):ワード単位でSIMDや通常のCPU命令で一度にWビット分を処理するため非常に高速。
- 個数カウント(popcount):CPUのPOP CNT命令や組み込み関数(例:GCC/Clang の __builtin_popcountll)を使用。ARMでは vcnt 系命令や NEON による並列処理を使える。
- 最下位セットビットの抽出および削除:x & -x(最下位1ビットのマスク)、x & (x-1)(最下位1ビットをクリア)などのビットトリックは高速かつ分岐なしで使える。
- 最小/最大ビット位置の取得:x86 の BSF/BSR(ビットスキャン)命令や同等の組み込みを使う。これらでセットされている最下位/最上位の位置をO(1)で求められる。
- ビット抽出/配置(compress/expand):BMI2 の PEXT/PDEP 命令(利用可能な環境で)で任意のマスクに基づく抽出/挿入が高速にできる。
- SIMD・ワイド演算:AVX/NEON などで複数ワードを同時処理し、複雑なブロック単位の操作を高速化できる。
典型的な用途(ユースケース)
- 集合の表現:集合演算をビット演算で行えるため、集合の和・積・差が高速。
- インデックスと検索(情報検索):倒立インデックスをビットマップで管理し、検索クエリのAND/ORを高速に処理するデータベースや検索エンジンで利用される。
- Bloomフィルタや類似構造:確率的集合構造としてハッシュでマッピングされたビット配列を利用。
- 圧縮インデックス・ビットマップ圧縮:大量のビット列を効率良く格納できる(下節参照)。
- グラフアルゴリズム・パターンマッチング:ビット並列アルゴリズム(例:Myers の編集距離アルゴリズム)やビットDPにより高速化。
- バイオインフォマティクス:ゲノム配列のフラグ、パターン存在など大量データでの真偽管理。
圧縮・エンコーディング手法
そのままのビットベクトルは密なデータ(多くのランダムな1と0)に対しては効率的ですが、疎な/繰り返しの多いビット列に対しては圧縮が効果的です。代表的手法:
- ランレングス圧縮(RLE):連続する同じビット長を記録。単純でランの長さが長いデータに有利。
- WAH / EWAH / Concise:ワード単位のラン長圧縮方式で、ブロックを「リテラル」と「ラン」で表す。ワード境界に合わせることで高速なAND/ORが可能。
- Roaring Bitmap:32ビット値の集合をブロック(通常は上位16ビットで分割)ごとにコンテナを選択(配列コンテナ、ビットマップコンテナ、ランコンテナ)し、実用上非常に高速で圧縮率も良い。実装(CRoaring, JavaRoaringBitmap 等)が豊富。
- RRR(Raman–Raman–Rao)やその他の「succinct」手法:統計的に小さい追加情報でランク/セレクトを高速に実装可能な圧縮表現を提供。ランダムアクセスやrank/selectをサポートする場面で有用。
rank / select と縮約(succinct)データ構造
ビットベクトルに対してよく求められる操作に、次の2つがあります。
- rank(i):先頭から位置 i までに1が何個あるかを返す。
- select(k):k番目の1が現れる位置を返す。
これらをO(1)時間でサポートするために追加のインデックス(スーパーブロック、サブブロック、ルックアップテーブル等)を用いることが多く、Jacobson や Clark & Munro、Raman らの研究により「元のビット長に対してごく小さな冗長(o(n)ビット)」で実現できることが示されています。RRR エンコーディングは圧縮しつつ rank/select を高速に行う代表的手法の1つです。
言語・ライブラリのサポート(実装例)
- C++:std::bitset(コンパイル時サイズ固定)、boost::dynamic_bitset(実行時可変)。また CRoaring(C ライブラリ)や simd 対応ライブラリがある。
- Java:java.util.BitSet(動的拡張)、RoaringBitmap(高性能な圧縮ビットマップ)など。
- Python:組み込みの int をビット列として利用可能だが、大規模用途ではサードパーティの bitarray モジュールや pyroaring 等が現実的。
- 専用ライブラリ:CRoaring(Roaring のC実装)、JavaRoaringBitmap、EWAHBitmap などがあり、実運用での性能・メモリ効率が検証されている。
パフォーマンスと実践的な注意点
ビットベクトルを実用で使う際のポイント:
- キャッシュフレンドリネス:連続するワードにアクセスする場合はキャッシュライン(通常64バイト)を意識した配置が重要。ワード境界で小さな処理を多数するよりも、まとまったブロック単位で処理する方が高速。
- アラインメント:SIMDや高速命令を使う場合はメモリアラインメントが性能に影響する。
- スレッド安全性:複数スレッドから同じビットベクトルを更新する場合は原子操作(atomic bitwise)やロックが必要。64ビット単位での原子読み書きは CPU が保証するが、複数ビットにまたがる更新は整合性に注意。
- シリアライズとエンディアン:ビット順序(LSBが先かMSBが先か)やワードの並びを明示的に定義しないと、異プラットフォーム間でのやり取りで不整合が生じる。
- ベンチマーク:実装ごとに性能特性が大きく異なるため、対象データ(疎/密、ラン長、アクセスパターン)での実測が重要。単に理論上の時間複雑度だけで選ばない。
高度なテクニックとアルゴリズム応用
- ビット並列アルゴリズム:例えば文字列編集距離の Myers アルゴリズムでは、各文字の位置をビットベクトルで持つことで文字単位の比較をワード並列に行い高速化する。
- 部分集合列挙:ビットトリックを用いると、ある集合のすべての部分集合を効率よく列挙できる(for (s = x; s; s = (s-1) & x) など)。
- 圧縮コンテナの選択:Roaring のように密度に応じてコンテナを動的に変えると、ランダム性の高いデータでも高性能を保てる。
- GPU活用:GPUではビット演算や並列集約(popcountの並列化)を大量データで効率的に行えるが、メモリ転送コストとGPU命令の制限に注意。
落とし穴とよくある誤解
- 「ビットベクトルは常にメモリ効率が良い」わけではない:高密度(多数の1)が頻繁にランダムに現れる場合、圧縮の効果は限定的で、逆に圧縮のオーバーヘッドで遅くなることがある。
- 可変長ビットベクトルの実装差:言語組込みの BitSet とサードパーティ実装では挙動(拡張時のゼロ初期化、クリアの挙動など)が異なることがあるため、仕様を確認すること。
- 端末環境依存の最適化:PEXT/PDEP や POPCNT の有無で大きく性能が変わるため、移植性を考慮したフォールバック実装が必要。
まとめ
ビットベクトルは、単純ながら強力なデータ構造であり、メモリ効率・ワード並列性・高速ビット演算を活かして多様な応用で重要な役割を果たします。用途に応じて素朴なビット配列、圧縮ビットマップ(WAH/EWAH/Concise)、Roaring のような実用的な圧縮形式、あるいは rank/select をサポートする縮約データ構造を選ぶと良いでしょう。実装ではCPU命令セット、キャッシュ、並列性、スレッド安全性、シリアライズ方針などを総合的に考慮して最適化することが鍵になります。
参考文献
- Bit array — Wikipedia
- Bitset — Wikipedia
- Rank and select — Wikipedia
- RRR (Raman–Raman–Rao) — Wikipedia
- Population count (POPCNT) — Wikipedia
- Bit scan (BSF/BSR) — Wikipedia
- BMI2 (PEXT/PDEP) — Wikipedia
- Roaring Bitmap — 公式サイト
- CRoaring — GitHub
- java.util.BitSet — Java ドキュメント
- Bloom filter — Wikipedia
- EWAH — GitHub (エンコード方式の実装例)
- Myers のビット並列アルゴリズム — Wikipedia


