2013-08-20 12 views
5

データ構造は、連続したいくつかのコンテナを操作し、データを含むこの配列内の連続した範囲のインデックスを迅速に取得できることを想像してください。この範囲を「ブロック」と呼ぶことにしましょう。各ブロックは、その頭と尾のインデックスを知っている:高速連続領域検索を伴うデータ構造

struct Block 
{ 
    size_t begin; 
    size_t end; 
} 

我々は、配列、我々のデータ構造の更新ブロックを操作する場合:

array view   blocks [begin, end] 
-------------------------------------------------------------- 
0 1 2 3 4 5 6 7 8 9  [0, 9] 

pop 2     block 1 splitted 

0 1 _ 3 4 5 6 7 8 9  [0, 1] [3, 9] 

pop 7, 8    block 2 splitted 

0 1 _ 3 4 5 6 _ _ 9  [0, 1] [3, 6] [9, 9] 

push 7     changed end of block 3 

0 1 _ 3 4 5 6 7 _ 9  [0, 1] [3, 7] [9, 9] 

push 5     error: already in 

0 1 _ 3 4 5 6 7 _ 9  [0, 1] [3, 7] [9, 9] 

push 2     blocks 1, 2 merged 

0 1 2 3 4 5 6 7 _ 9  [0, 7] [9, 9] 

をしてもプロファイリングする前に、我々はブロック検索速度は、アプリケーションのパフォーマンスの礎になることを知っています。 基本的用法は次のとおりです。

  • 非常に多くの場合、連続したブロックの検索
  • 非常にまれ挿入/欠失
  • 我々が持っている私たちはブロックの数を最小限にしたいほとんどの時間(断片化を防ぐ)

既に試しました:

  1. std::vector<bool> + std::list<Block*>。すべての変更:true/falseをvectorに書き込んだ後、forループ内をトラバースし、listを再生成します。ブロックのクエリごとにlistを返します。我々が望んでいたよりも遅い。

  2. std::list<Block*>リストを直接更新することはありません。リストを返す。多くのコードをデバッグ/テストします。

質問:

  1. データ構造は、いくつかの一般的な名前を持っていることですか?
  2. 既に実装されている(デバッグされ、テストされた)データ構造はありますか?
  3. もしそうでなければ、そのようなデータ構造の迅速かつ堅牢な実装について何をアドバイスできますか?

私の説明が明確でない場合は申し訳ありません。このコンテナの

編集

典型的なアプリケーションでは、バッファを管理している:システムまたはGPUのメモリのいずれかを。 GPUの場合、単一の頂点バッファに大量のデータを格納し、一部の領域を更新/無効化することができます。各ドローコールでは、描画するバッファ内の各有効なブロックの最初と最後のインデックスを知る必要があります(たいていは10〜100回/秒)。時には(1秒に1回)データのブロックを挿入/削除する必要があります。

別のアプリケーションはカスタムの「ブロックメモリアロケータ」です。その目的のために、同様のデータ構造が "Alexandrescu A. - Modern C++ Design"ブックに侵入型リンクリストを介して実装されています。私はより良いオプションを探しています。

+0

私はしばしば、ツリーやベクトルに基づいてこのような「離散的な範囲」のタイプを開発しましたが、そのようなことがすでに増強されていると聞きました。 –

+0

コンテナに範囲が含まれているか、または個別のインデックス範囲にデータが含まれていますか? –

+0

私たちのアプリケーションでは、単純なバッファ(動的配列)上の何らかのラッパーでなければなりません。私は、GPUバッファのユースケースで編集を追加しました。わかりやすくするために、基底のコンテナは 'std :: vector 'とすることができます。バッファインターフェイス(読み込み/書き込み/更新/マークフリー)は実装するのが難しくありませんが、この "ブロック"に固執しています。 – Drop

答えて

4

単純な赤黒のツリーまたはB +ツリーのような構造のツリーを試してみるとよいでしょう。

+0

木は素晴らしいです。そして、彼らは難しいです。それを私の場合に適用する方法についてもっと詳しく分かりますか?また、私はC++で人気のある、堅牢な、普遍的なツリーの実装について認識していません。私自身の書き込み/デバッグ/テストは良い選択肢ではありません。 – Drop

+0

@Drop Google検索では、入力するよりも多くの情報が得られます。 –

1

あなたの最初の解決策(ブールのベクトル+ブロックのリスト)は良い方向のようですが、リストをゼロから完全に再生成する必要はありません。新しく変更されたインデックスが修正される場所を見つけるまでリストを走査し、リスト上の適切なブロックを分割/マージします。

リストトラバーサルが長すぎると判明した場合は、ブロックのベクトルを実装することができます。各ブロックは開始インデックスにマップされ、各穴には穴の終了位置を示すブロックがあります。このベクトルをリストのように速くトラバースするには、常に次のブロック(1つのO(1)ルックアップでブロックの終わりを判断し、別のO(1)ルックアップで次のブロックの開始を判断する)しかし、インデックスに直接アクセスして(push/popのために)、バイナリ検索で囲むブロックを見つけることができます。 それを機能させるには、 "穴"(マージと重要なのは、ブロック間には常に1つの穴があり、その逆もあります。

+0

ええ、リストのベクトル模倣は面白いと思う。そして、私はこれが大きなメモリトレードオフだと思います。しかし、断片化を低く抑えれば、この解決策は非常に実用的です。ありがとう! – Drop

4

ここで見ているのはシンプルですバイナリツリー
beginとのペア(ブロック)があります。インデックス、つまり、(a,b)のペアは、a <= bです。したがって、ブロックのセットは、検索バイナリツリーに簡単に注文して格納することができます。
指定された番号に対応するブロックを検索するのは簡単です(ヒントツリーバイナリーツリー検索のみ)。したがって、配列から数値を削除する場合は、その数値に対応するブロックを検索し、2つの新しいブロックに分割する必要があります。 すべてのブロックはリーフであり、内部ノードは2つの子ノードがを形成する間隔です。
一方、挿入とはブロックを検索し、兄弟が倒れなければならないかどうかを調べることです。これはツリーを再帰的に実行する必要があります。

+0

さて、木。適切なバイナリツリーの実装についてアドバイスできますか?私はそれを最初から書いているとは信じていません。おそらく 'std :: set'や' std :: map'を採用することができますか? – Drop

+0

@ここで問題となるのは 'std :: map'と' std :: set'は木ですが、連想型コンテナとして動作するように設計されています。私たちのツリーは、カスタムデータと振る舞いを持つバイナリツリーです。だからあなたは一からそれを実装する必要があります。 – Manu343726

0

なぜブロックのリストを使用していますか?安定したイテレーターと安定したリファレンスが必要ですか? boost :: stable_vectorが役に立ちます。安定した参照を必要としない場合は、std :: vectorブロックとサイズblocks.capacity()の二次メモリマップを含むラッパーコンテナを作成することです。イテレータインデックスからのマップです(保持されます)。内部はブロック・ベクタ内の実際のオフセットにイテレータを戻しました)と、現在使用されていないイテレータ・インデックスのリストを返します。

ブロックからメンバーを消去すると、ブロックを再パックし、それに応じてキャッシュの一貫性を高めるためにマップをシャッフルします。挿入するときは、ブロックにpush_backだけを挿入します。

ブロックパッキングの場合、削除速度を犠牲にして反復処理を行うと、キャッシュの一貫性が得られます。そして比較的速い挿入時間を維持する。

また、安定した参照とイテレータが必要な場合や、コンテナのサイズが非常に大きい場合は、アクセス速度、反復速度、キャッシュの一貫性を犠牲にして、単純にベクトルの各エントリをラップします実際のエントリと次の有効なオフセットを含む構造体を作成するか、ベクトルにポインタを格納し、削除時にnullにします。

関連する問題