16

この質問について考えるとき、私は、std::copy()および/またはstd::fillに特化されている(私は本当に最適化されている)かどうか疑問に思い始めstd::vector<bool>ます。

これはC++標準で必要ですか、それともC ++ stdライブラリベンダーによる一般的なアプローチですか?

簡単に言えば、次のコードがあるかどうか知りたいです。

std::vector<bool> v(10, false);
std::fill(v.begin(), v.end(), true);

それよりも優れている/異なる:

std::vector<bool> v(10, false);
for (auto it = v.begin(); it != v.end(); ++it) *it = true;

非常に厳密に言うと、たとえば、次のようにできます。単一ビットではなく、バイト全体std::fill<std::vector<bool>::iterator>()の内部表現に移動して設定しますか?友達をstd::vector<bool>作ることは図書館のベンダーにとって大きな問題ではないと思いますか?std::fillstd::vector<bool>

[アップデート]

次の関連する質問:私(または他の誰か:)は、まだ専門化されstd::vector<bool>ていない場合、たとえば、そのようなアルゴリズムを専門化できますか?これはC++標準で許可されていますか?これは移植性がないことはわかっていますが、選択した1つの標準C ++ライブラリだけですか?私(または他の誰か)がstd::vector<bool>プライベートパーツに到達する方法を見つけたと仮定します。

4

4 に答える 4

13

STDはヘッダーのみのライブラリであり、コンパイラに付属しています。これらのヘッダーを自分で調べることができます。GCCのvector<bool> 推進力はにありstl_bvector.hます。おそらく他のコンパイラでも同じファイルになるでしょう。そして、はい、専門的ですfill(近くを見てください__fill_bvector)。

于 2012-09-15T05:07:33.497 に答える
4

最適化は、標準ではどこにも義務付けられていません。最適化を適用できれば、「実装の品質」の問題であると見なされます。ただし、ほとんどのアルゴリズムの漸近的な複雑さには制限があります。

最適化は、標準で義務付けられているとおりに正しいプログラムが動作する限り許可されます。質問する例、つまり、でイテレータを使用する標準アルゴリズムを含む最適化はstd::vector<bool>、実装方法を監視する方法がないため、実装が適切と考える方法でほぼ目的を達成できます。とはいえ、操作を最適化する標準ライブラリの実装があるかどうかは非常に疑わしいstd::vector<bool>です。ほとんどの人は、この専門分野はそもそも忌まわしいものであり、それはなくなるべきだと考えているようです。

ユーザーは、特殊化に少なくとも1つのユーザー定義型が含まれる場合にのみ、ライブラリー型の特殊化を作成できます。ユーザーが名前空間で関数を提供することはまったく許可されていないと思いますstd。そのような関数はすべてユーザー定義型を含み、したがってユーザーの名前空間にあるため、必要はありません。別の方法で定式化する:当面の間、アルゴリズムを最適化することに関して、あなたは運が悪いと思いますstd::vector<bool>。ただし、最適化されたバージョンをオープンソースの実装(libstdc++およびなどlibc++)に提供することを検討することもできます。

于 2012-09-15T01:05:37.253 に答える
1

専門分野はありませんが、引き続きご利用いただけます。(遅いですが)

しかし、これが私が見つけたトリックで、プロキシクラスを使用してを有効std::fillにします。std::vector<bool>std::_Vbase

(警告:MSVC2013でのみテストしたため、他のコンパイラでは動作しない可能性があります。)

int num_bits = 100000;
std::vector<bool> bit_set(num_bits , true);

int bitsize_elem = sizeof(std::_Vbase) * 8; // 1byte = 8bits
    
int num_elems = static_cast<int>(std::ceil(num_bits / static_cast<double>(bitsize_elem)));

ここでは、要素のビットを使用する場合は要素のビット全体が必要になるため、要素の数を切り上げる必要があります。

この情報を使用して、ビットの基になる元の要素を指すポインターのベクトルを作成します。

std::vector<std::_Vbase*> elem_ptrs(num_elems, nullptr);

std::vector<bool>::iterator bitset_iter = bit_set.begin();
for (int i = 0; i < num_elems; ++i)
{
    std::_Vbase* elem_ptr = const_cast<std::_Vbase*>((*bitset_iter)._Myptr);
    elem_ptrs[i] = elem_ptr;
    std::advance(bitset_iter, bitsize_elem);
}

(*bitset_iter)._Myptr:のイテレータを逆参照することにより、プロキシクラスとそのメンバーにstd::vector<bool>アクセスできます。reference_Myptr

の戻り型はですので、std::vector<bool>::iterator::operator*()その恒常性を。const std::_Vbase*削除const_castします。

これで、これらのビットの基になる元の要素を指すポインタを取得しますstd::_Vbase* elem_ptr

elem_ptrs[i] = elem_ptr:このポインタを記録します、..。

std::advance(bitset_iter, bitsize_elem):...そして、前の要素によって保持されているビットをジャンプすることによって、次の要素を見つけるために私たちの旅を続けます。

std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, 0); // fill every bits "false"
std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, -1); // fill every bits "true"

std::fillこれで、ビットのベクトルではなく、ポインターのベクトルを使用できるようになりました。

おそらく、プロキシクラスを外部で使用することに不快感を覚えたり、プロキシクラスの恒常性を取り除いたりする人もいるかもしれません。

しかし、それを気にせず、何かを速くしたい場合は、これが最速の方法です。

以下でいくつかの比較を行いました。(新しいプロジェクトを作成しました。構成、リリース、x64は何も変更されていません)

int it_max = 10; // do it 10 times ...
int num_bits = std::numeric_limits<int>::max(); // 2147483647

std::vector<bool> bit_set(num_bits, true);
for (int it_count = 0; it_count < it_max; ++it_count)
{
    std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, 0);
} // Elapse Time : 0.397sec

for (int it_count = 0; it_count < it_max; ++it_count)
{
    std::fill(bit_set.begin(), bit_set.end(), false);
} // Elapse Time : 18.734sec

for (int it_count = 0; it_count < it_max; ++it_count)
{
    for (int i = 0; i < num_bits; ++i)
    {
        bit_set[i] = false;
    }
} // Elapse Time : 21.498sec

for (int it_count = 0; it_count < it_max; ++it_count)
{
    bit_set.assign(num_bits, false);
} // Elapse Time : 21.779sec

for (int it_count = 0; it_count < it_max; ++it_count)
{
    bit_set.swap(std::vector<bool>(num_bits, false)); // You can not use elem_ptrs anymore
} // Elapse Time : 1.3sec

注意点が1つあります。swap()元のベクトルを別のベクトルと一緒にすると、ポインターのベクトルは役に立たなくなります。

于 2020-09-26T07:59:53.457 に答える
0

23.2.5 C ++国際標準からのクラスベクトルは、私たちに伝えるところまで行きます

スペースの割り当てを最適化するために、bool要素のベクトルの特殊化が提供されています。

その後、ビットセットの特殊化が提供されます。これは、標準に関する限りvector<bool>、ベンダーはスペースを最適化するためにビットセットを使用して実装する必要があります。スペースの最適化には、速度の最適化を行わないため、コストが伴います。

コンテナに密接にホチキス止めされたすべての図書館の本の間にある場合、本を見つけるよりも、図書館から本を入手する方が簡単です。


あなたの例を見てください、あなたは最初から最後までstd::fillまたはを行おうとしていますstd::copy。ただし、常にそうであるとは限りません。単にバイト全体にマップするだけではない場合もあります。ですから、それは速度の最適化という点で少し問題です。すべてのビットを1に変更する必要がある場合、つまりバイトを0xFに変更するだけの場合は簡単ですが、ここではそうではありません。バイトの特定のビットのみを変更する場合は、はるかに困難になります。次に、バイトがどうなるかを実際に計算する必要があります。これは簡単なことではありません*、または少なくとも現在のハードウェアでの不可分操作としてではありません。

これは時期尚早の最適化ストーリーであり、スペースの点では優れていますが、パフォーマンスの点では恐ろしいものです。

"is a multiple of 8 bits"オーバーヘッドの価値があるチェックを持っていますか?疑わしい。

*ここでは複数のビットについて説明しています。1ビットの場合は、もちろんビット演算を実行できます。

于 2012-09-15T01:00:22.663 に答える