19

私は現在、パフォーマンスの問題が発生している組み込みデバイス プロジェクトに取り組んでいます。プロファイリングにより、削除したい O(N) 操作が見つかりました。

私は基本的に2つの配列int A[N]short B[N]. のエントリAは一意であり、外部制約によって順序付けられます。最も一般的な操作は、特定の値aが に表示されるかどうかを確認することA[]です。あまり頻繁ではありませんが、依然として一般的なのは、 の要素への変更ですA[]。新しい値は以前の値とは無関係です。

最も一般的な操作は検索であるため、そこに出番があります。これは、B[]インデックスの並べ替えられた配列です。つまり、バイナリ検索を使用して値を見つけることができます。A[]A[B[i]] < A[B[j]]i<jA

もちろん、更新するときはA[k]、検索順序を維持するために、検索して新しい位置に移動する必要がありますkBの古い値と新しい値を知っているので、それは の古い位置と新しい位置の間ののサブセットにA[k]すぎません。これは、修正する必要がある O(N) 操作です。の古い値と新しい値は本質的にランダムであるため、平均して約N/2 N/3 要素を移動しています。memmove()B[]kA[k]

述語としてのstd::make_heap使用を検討しました。その場合、 の最小要素を[](int i, int j) { return A[i] < A[j]; }簡単に指摘でき、更新は安価な O(log N) リバランス操作になりました。ただし、通常、A の最小値は必要ありません。特定の値が存在するかどうかを確認する必要があります。そして、これは の O(N log N) 検索になりました。(私の N 要素の半分はヒープ深さ log N にあり、4 分の 1 は (log N)-1 にあるなど)、.B[0]ABBA

O(log N) の挿入と検索があることを考えるstd::setと、更新と検索で同じパフォーマンスを得ることができるはずです。しかし、どうすればそれを行うことができますか?をもう一度注文する必要がありBますか? 違うタイプ?

B現在、short [N]AB合わせると、CPU キャッシュのサイズとほぼ同じであり、メイン メモリがかなり遅いためです。6*N バイトから 8*N バイトになるのは良くありませんが、検索と更新が両方とも O(log N) になる場合は許容できます。

4

3 に答える 3

7

操作が (1) 値 'a' が A に属しているかどうかを確認し、(2) A の値を更新するだけの場合、並べ替えられた配列 B の代わりにハッシュ テーブルを使用してみませんか? 特に、 A のサイズが拡大または縮小せず、値のみが変化する場合、これははるかに優れたソリューションになります。ハッシュ テーブルは、配列よりも大幅に多くのメモリを必要としません。(代わりに、B をヒープではなく二分探索木に変更する必要があります。これは、分散木や赤黒木などの自己均衡を保つことができます。しかし、木は、左と右が原因で追加のメモリを必要とします。ポインター。)

メモリ使用量を 6N から 8N バイトに増やす実用的な解決策は、正確に 50% のハッシュ テーブルを使用することです。つまり、2N の short の配列で構成されるハッシュ テーブルを使用します。Cuckoo Hashingメカニズムを実装することをお勧めします( http://en.wikipedia.org/wiki/Cuckoo_hashingを参照)。記事をさらに読むと、より多くのハッシュ関数を使用することで、50% を超える負荷率を得ることができる (つまり、メモリ消費量を 8N から 7N に押し下げる) ことができることがわかります。"ハッシュ関数を 3 つだけ使用すると、負荷が 91% に増加します。 "

ウィキペディアから:

Zukowski らによる研究。最近のプロセッサの小さなキャッシュ常駐ハッシュテーブルでは、カッコウハッシュが連鎖ハッシュよりもはるかに高速であることを示しています. Kenneth Ross は、カッコウ ハッシュ (複数のキーを含むバケットを使用するバリアント) のバケット化されたバージョンが、スペース使用率が高い場合、大きなハッシュ テーブルに対しても従来の方法よりも高速であることを示しました。バケット化されたカッコウ ハッシュ テーブルのパフォーマンスは、Askitis によってさらに調査され、そのパフォーマンスが別のハッシュ スキームと比較されました。

于 2012-05-11T13:17:24.380 に答える
1

std::set通常、二分探索木を使用してO(log(n))の挿入と削除を提供します。残念ながら、これはほとんどのポインタベースの実装に3*Nスペースを使用します。ワードサイズのデータ​​を想定します。1つはデータ用、2つは各ノードの左右の子へのポインター用です。

ceil(log2(N))定数Nがあり、それがワードサイズの半分未満であることを保証できる場合は、各2*Nサイズのツリーノードの固定長配列を使用できます。単語の上半分と下半分として格納されているデータに1を使用し、2つの子ノードのインデックスに1を使用します。これにより、何らかの方法で自己平衡二分探索木を使用できるかどうかは、Nとワードサイズによって異なります。16ビットシステムの場合はN=256しか得られませんが、32ビットシステムの場合は65kになります。

于 2012-05-11T14:14:11.657 に答える
0

Nが限られているのでBooststd::set<short, cmp, pool_allocator> Bで使えないの?pool_allocator

于 2012-05-11T14:10:35.897 に答える