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


16

私はほとんどC ++ 11/14でのみ動作しますが、通常、次のようなコードが表示されたときはうんざりします。

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

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

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

私の質問は 下位互換性以外に、プリミティブ型を使用しない理由はありますstd::bitsetか?


のサイズはstd::bitsetコンパイル時に固定されます。それは私が考えることができる唯一の不自由な欠点です。
ルワン

1
@rwong std::bitset対Cスタイルのビット操作(例int)について話しているが、これもコンパイル時に修正される。
Quant

1つの理由はレガシーコードである可能性があります。コードはstd::bitset利用できない(または作成者に知られている)ときに書かれており、使用するコードを書き換える理由はありませんでしたstd::bitset
バートヴァンインゲンシェナウ

個人的には、「バイナリ変数のセット/マップ/配列の操作」を誰にでも理解しやすくする方法の問題は、まだほとんど解決されていないと考えています。また、そのようなセットを表現する方法は多すぎますbitsetが、そのうちの1つですが、小さなベクトルまたはintsのセット(ビットインデックスの)も正当な場合があります。C / C ++の哲学は、これらの選択の複雑さをプログラマから隠しません。
ルウォン

回答:


12

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

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

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

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

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

ただし、組み込みの要件と制限を追加のコードで克服することはできません。以下では、のサイズstd::bitsetはコンパイル時の定数であると主張します。したがって、デメリットとしてはカウントされませんが、それでもユーザーの選択に影響するものです。


審美的な観点(読みやすさ、保守の容易さなど)からは、違いがあります。

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


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


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

しかしながら、bitsetまたなどの他のオペレータ、有しoperator &=operator <<=ビット集合の全幅に動作する、など。基礎となる機械は多くの場合、一度に(同じCPUサイクルで)32ビット、64ビット、場合によっては128ビット(SIMDを使用)で動作できるため、このようなマルチビット操作を利用するように設計されたコード「ループ」ビット操作コードよりも高速です。

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


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


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

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

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

2番目の方法は、標準が拡張可能でない場合でも、タスクを実行するための何らかの種類のアダプターを作成できるため、克服できますbitset


すぐには提供されない特定のタイプの高度なSWAR操作がありますstd::bitsetビット順列についてはこのWebサイトでこれらの操作について読むことができます。通常どおり、これらを独自に実装し、の上で動作させることができますstd::bitset


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

1つの警告:多くの人が、標準ライブラリの(何か)が単純なCスタイルのコードよりもはるかに遅い理由について尋ねます。ここではマイクロベンチマークの前提知識を繰り返しませんが、このアドバイスがあります。「リリースモード」でベンチマークを行い(最適化を有効にして)、コードが削除ない(デッドコードの削除)またはループ(ループ不変コードの動き)から引き上げられました

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


問題#2は、ビットセットのサブセットで各スレッドが動作する並列セットアップではビットセットを使用できないことも意味します。
user239558

@ user239558誰も同じで並列化したいとは思わないでしょうstd::bitset。メモリの一貫性の保証(in std::bitset)はありません。つまり、コア間で共有されることは想定されていません。コア間で共有する必要がある人は、独自の実装を構築する傾向があります。異なるコア間でデータが共有される場合、それらをキャッシュライン境界に合わせるのが一般的です。そうしないと、パフォーマンスが低下し、非原子性の落とし穴が多くなります。の並列化可能な実装を構築する方法の概要を説明するのに十分な知識がありませんstd::bitset
rwong

データ並列プログラミングは通常、メモリの一貫性を必要としません。フェーズ間でのみ同期します。私は絶対にビットセットを並行して処理したいと思います、私は大きなbitset意志を持つ人だと思います。
user239558

@ user239558はコピーを意味します(各コアで処理されるビットセットの関連範囲は、処理が開始される前にコピーされます)。私はそれに同意しますが、並列化を考えている人はすでに自分の実装の展開を考えていると思います。一般に、多くのC ++標準ライブラリ機能がベースライン実装として提供されています。より深刻なニーズがある人は誰でも自分で実装します。
rwong

いいえ、コピーはありません。静的データ構造のさまざまな部分にアクセスするだけです。その場合、同期は不要です。
user239558

2

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

簡単な例として、勝利のためにコネクトフォーのポジションをテストする次の関数(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
std::bitset遅いと思いますか?
Quant

さて、ソースを一目見ただけで、libc ++ビットセットは単一のsize_tまたはそれらの配列に基づいているため、特にsizeof(size_t)== 8のシステムでは、本質的に同等/同一のものにコンパイルされるでしょう。いいえ、おそらくそれ以上遅くなることはありません。
ライアンパブリク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.