web-dev-qa-db-ja.com

最速の反復のためのsetとunordered_set

私のアプリケーションでは、次の要件があります-

  1. データ構造には、いくつかの値(キーと値のペアではない)が1回だけ入力されます。値は繰り返される場合がありますが、データ構造に1回だけ保存する必要があります。

  2. 上で作成したデータ構造のすべての要素を何百回も繰り返します。反復で要素が表示される順序は重要ではありません。

制約1は、データがキーと値のペアの形式ではないため、setまたはunordered_setを使用する必要があることを示しています。

現在、セットの挿入はunordered_setの挿入よりもコストがかかりますが、データ構造はプログラムの最初に1回だけ入力されます。

決定的な要因は、データ構造のすべての要素をどれだけ速く反復できるかということだと思います。この目的のために、setまたはunordered_setのどちらが高速になるかはわかりません。この操作はどちらのデータ構造でもO(n)になるため、標準ではこの事実については言及されていないと思います。しかし、どちらのデータ構造iterator.next()の方が高速になるのでしょうか。

16
Aviral Goel

いくつかのアプローチがあります。

  1. あなたの質問へのコメントは、(すべてのコンテナがそうであるように)最速のO(1)ルックアップ/挿入とO(N)反復を持つ_std::unordered_set_を維持することを示唆しています。大きく変化するデータがある場合、または多くのランダムルックアップが必要な場合は、これがおそらく最速です。しかし、test
  2. 中間挿入なしで数百回繰り返す必要がある場合は、1回のO(N)コピーを_std::vector_に実行し、連続したメモリレイアウトから数百回取得できます。 これが通常の_std::unordered_set_よりも速いかどうかをテストします。
  3. 反復間の中間挿入の数が少ない場合は、専用のベクトルを使用することで費用が発生する可能性があります。 Boost.Container を使用できる場合は、_boost::flat_set_インターフェイスを提供する_std::set_を試してください。 _std::vector_ストレージバックエンド(つまり、キャッシュとプリフェッチに非常に適した連続したメモリレイアウト)。繰り返しますが、testこれが他の2つのソリューションのスピードアップをもたらすかどうか。

最後の解決策については、いくつかのトレードオフについてBoostのドキュメントを参照してください(イテレータの無効化、移動のセマンティクス、例外の安全性など、他のすべての問題にも注意することをお勧めします)。

Boost.Container flat_ [multi] map/setコンテナーは、AusternおよびAlexandrescuのガイドラインに基づいた順序付きベクトルベースの連想コンテナーです。これらの順序付けられたベクトルコンテナは、最近、C++に移動セマンティクスが追加され、挿入と消去の時間が大幅に短縮されたという利点もあります。フラット連想コンテナには、次の属性があります。

  • 標準の連想コンテナよりも高速なルックアップ
  • 標準の連想コンテナよりもはるかに高速な反復
  • 小さいオブジェクト(およびshrink_to_fitが使用されている場合は大きいオブジェクト)のメモリ消費量が少なくなります
  • キャッシュパフォーマンスの向上(データは連続したメモリに保存されます)
  • 不安定なイテレータ(要素の挿入および消去時にイテレータは無効になります)
  • コピー不可および移動不可の値タイプは保存できません
  • 標準の連想コンテナよりも弱い例外安全性(消去および挿入で値をシフトするときにコピー/移動コンストラクターがスローする可能性があります)
  • 標準の連想コンテナよりも挿入と消去が遅い(特に移動不可能なタイプの場合)

[〜#〜] note [〜#〜]:ルックアップが高速になると、_flat_set_がO(log N)を実行することを意味します通常の_std::set_のO(log N)ポインタ追跡ではなく、連続したメモリ上。もちろん、_std::unordered_set_はO(1)ルックアップを実行します。これは、大きなNの場合に高速になります。

16
TemplateRex

「フィルタリング」にはsetまたはunordered_setのいずれかを使用することをお勧めします。完了したら、データを固定サイズのベクトルに移動します。

5

データ構造の構築がパフォーマンスの懸念を考慮に入れていない場合(または少なくともわずかに)、データをstd::vectorに保存することを検討してください。これに勝るものはありません。

データ構造の初期構築を高速化するために、最初にstd::unordered_setに挿入するか、挿入前に存在を確認するために少なくとも1つを使用することができます。

2番目のケースでは、要素を含める必要はありませんが、たとえば、インデックス。

std::vector<T> v;
auto h = [&v](size_t i){return std::hash<T>()(v[i]);};
auto c = [&v](size_t a, size_t b){return v[a] == v[b];};
std::unordered_set<size_t, decltype(h), decltype(c)> tester(0, h, c);
4
Deduplicator

このような場合に使用することを強くお勧めしますnotsetは二分木で、unordered_setはハッシュテーブルです。そのため、大量のメモリを使用し、反復速度が遅く、参照の局所性が低くなります。データを頻繁に挿入/削除/検索する必要がある場合は、setまたはunordered_setを選択しますが、データの読み取り、保存、並べ替えを1回行うだけで、データのみを何度も使用します。

この場合、sorted vectorは非常に良い選択です。 vectorは動的arrayであるため、オーバーヘッドが低くなります。

直接、コードを参照してください。

std::vector<int> data;

int input;
for (int i = 0; i < 10; i++)
{
    std::cin >> input;
    data.Push_back(input); // store data
}

std::sort(data.begin(), data.end()); // sort data

それで全部です。すべてのデータの準備ができています。

setのような重複を削除する必要がある場合は、並べ替え後にunique --eraseを使用してください。

data.erase(
    std::unique(data.begin(), data.end()),
    data.end()
    );

の利点を利用するには、findまたはlower_boundではなくupper_boundequal_range、およびfind_ifを使用する必要があることに注意してください。ソートされたデータ。

2
ikh

順序付けられていないセットは、ハッシュテーブルを使用してニアO(1)時間検索を提供します。これは、キーのハッシュを使用して要素のオフセットを計算することによって行われます。データセットの最初から(キー)をシークします。データセットが小さい場合(charsなど)を除いて、異なるキーが同じハッシュを持つ場合があります(衝突)。

衝突を最小限に抑えるために、順序付けられていないセットは、データストアにかなりまばらにデータを入力しておく必要があります。これは、キーを見つけるのがほとんどO(1)時間(衝突がない限り)であることを意味します。

ただし、ハッシュテーブルを反復処理する場合、イテレータはデータストア内の未使用のスペースを大量に検出するため、イテレータによる次の要素の検出が遅くなります。ハッシュテーブル内の隣接する要素を追加のポインターでリンクすることはできますが、順序付けられていないセットはそうしないと思います。

上記に照らして、「セット」にソートされたベクトルを使用することをお勧めします。二分法を使用すると、O(log n)時間でストアを検索でき、リストを反復処理するのは簡単です。ベクトルには、メモリが連続しているため、キャッシュミスが発生する可能性が低いという追加の利点があります。

2
doron