web-dev-qa-db-ja.com

std :: bitsetよりもCスタイルのビット操作に利点はありますか?

私はC++ 11/14でほぼ独占的に作業しており、通常、次のようなコードを見るとうんざりします。

std::int64_t mArray;
mArray |= someMask << 1;

これは単なる例です。私は一般的にビット単位の操作について話しています。 C++では、本当に何か意味がありますか?上記は心ゆがみでエラーが発生しやすいですが、std::bitsetを使用すると次のことができます。

  1. std::bitsetのサイズを必要に応じてテンプレートパラメータを調整し、残りの部分を実装に任せることで、より簡単に変更できます。
  2. 何が起こっているかを理解するのに費やす時間を減らし(そして間違いを犯す可能性もあります)、std::bitsetまたは他のデータコンテナーと同様の方法でstd::arrayを書き込みます。

私の質問です。 not下位互換性以外のプリミティブ型に対してstd::bitsetを使用する理由はありますか?

17
quant

論理的(非技術的)な観点からは、利点はありません。

プレーンなC/C++コードは、適切な「ライブラリ構造」内にラップできます。そのようなラッピングの後、「これがそれより有利であるかどうか」という問題は議論の余地のある質問になります。

速度の観点から見ると、C/C++では、ライブラリ構造がラップするプレーンコードと同じくらい効率的なコードを生成できるようにする必要があります。ただし、これは以下の制約を受けます。

  • 関数のインライン展開
  • コンパイル時のチェックと不要なランタイムチェックの排除
  • デッドコードの排除
  • 他の多くのコード最適化...

この種の非技術的な議論を使用すると、「不足している関数」は誰でも追加できるため、不利な点としては数えられません。

ただし、組み込みの要件と制限は、追加のコードでは克服できません。以下では、std::bitsetのサイズはコンパイル時の定数であるため、不利とは見なされませんが、それでもユーザーの選択に影響を与えるものです。


美的観点(読みやすさ、保守のしやすさなど)の点で違いがあります。

ただし、std::bitsetコードが単純なCコードよりもすぐに勝つことは明らかではありません。 std::bitsetの使用によってソースコードの人間の品質が向上したかどうかを判断するには、より大きなコード(おもちゃのサンプルではなく)を調べる必要があります。


ビット操作の速度は、コーディングスタイルによって異なります。コーディングスタイルはC/C++ビット操作の両方に影響し、以下で説明するように、std::bitsetにも同様に適用できます。


operator []を使用して一度に1ビットを読み書きするコードを書き込む場合、操作するビットが複数ある場合は、これを複数回行う必要があります。 Cスタイルのコードについても同じことが言えます。

ただし、bitsetには、operator &=operator <<=などの他の演算子もあり、ビットセットの全幅で動作します。基盤となる機械は、しばしば32ビット、64ビット、時には128ビット(SIMDを使用)で(同じCPUサイクル数で)動作できるため、このようなマルチビット操作を利用するように設計されたコード「ルーピー」ビット操作コードよりも高速である可能性があります。

一般的な考え方は SWAR(レジスタ内のSIMD) と呼ばれ、ビット操作のサブトピックです。


一部のC++ベンダーは、SIMDを使用して64ビットと128ビットの間にbitsetを実装する場合があります。一部のベンダーはそうしないかもしれません(しかし最終的にはそうするかもしれません)。 C++ベンダーのライブラリが何をしているかを知る必要がある場合、唯一の方法は逆アセンブリを調べることです。


std::bitsetに制限があるかどうかについて、2つの例を挙げます。

  1. std::bitsetのサイズはコンパイル時にわかっている必要があります。動的に選択されたサイズのビットの配列を作成するには、std::vector<bool>を使用する必要があります。
  2. std::bitsetの現在のC++仕様では、Mビットのより大きなbitsetからNビットの連続したスライスを抽出する方法が提供されていません。

最初のものは基本的なものです。つまり、動的サイズのビットセットを必要とする人は、他のオプションを選択する必要があります。

標準のbitsetが拡張可能でない場合でも、タスクを実行するために ある種のアダプタ を記述できるため、2つ目の方法は克服できます。


std::bitsetからそのままでは提供されない特定のタイプの高度なSWAR操作があります。 ビット順列についてのこのウェブサイト でこれらの操作について読むことができます。いつものように、std::bitsetの上で動作するこれらを自分で実装できます。


パフォーマンスに関する議論について。

一つの警告:多くの人々が、標準ライブラリの(something)が単純なCスタイルのコードよりもはるかに遅い理由を尋ねます。ここではマイクロベンチマークの前提知識を繰り返さないでください。ただし、このアドバイスがあります。「リリースモード」でベンチマークを行い(最適化を有効にして)、コードが削除されていないことを確認してください 除去(デッドコード)除去) または ループから外れた状態(ループ不変コードモーション) であること。

一般に、誰かが(インターネット上で)マイクロベンチマークを正しく実行していたかどうかを判断できないため、信頼できる結論を得るには、独自のマイクロベンチマークを実行して詳細を文書化し、パブリックレビューと批評に提出するしかありません。他の人が以前に行ったようなマイクロベンチマークをやり直すことは害にはなりません。

12
rwong

これは確かにすべての場合に当てはまるわけではありませんが、アルゴリズムがCスタイルのビット調整の効率に依存して、パフォーマンスを大幅に向上させる場合があります。私の頭に浮かぶ最初の例は、チェスエンジンなどを高速化するために、ボードゲームの位置の巧妙な整数エンコーディングである bitboards を使用することです。チェス盤は常に8 * 8なので、整数型の固定サイズは問題ありません。

簡単な例として、次の関数(Ben Jacksonによる この回答は、Ben Jacksonによる から取得)を検討してください。

// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
    uint64_t y = newboard & (newboard >> 6);
    uint64_t z = newboard & (newboard >> 7);
    uint64_t w = newboard & (newboard >> 8);
    uint64_t x = newboard & (newboard >> 1);
    return (y & (y >> 2 * 6)) | // check \ diagonal
           (z & (z >> 2 * 7)) | // check horizontal -
           (w & (w >> 2 * 8)) | // check / diagonal
           (x & (x >> 2));      // check vertical |
}
2
David Zhang