std::string
を 1 つだけ保存する価値があるかもしれません すべてのデータを組み合わせて、 std::string_view
を使用します マップで。これにより、必要なメモリ割り当てが 1 つだけになるため、ミューテックスの競合がなくなります。 string_view
単純なデストラクタがあるため、そのためのスレッドは必要ありません。
以前にこの手法を使用してプログラムを 2500% 高速化することに成功しましたが、これは、この手法によって総メモリ使用量が削減されたためでもあります。
std::vector
を使用して試すことができます メモリーの収納に。 std::vector
要素は連続して格納されるため、キャッシュ ミスが減少します (「キャッシュに適した」コードとは? を参照してください)。
したがって、map<???,size_t>
になります。 map<???,std::string>
の代わりに 文字列を取得するためにもう 1 つの間接処理が必要になりますが (実行時間のコストが余分にかかることを意味します)、キャッシュ ミスを大幅に減らしてすべての文字列を反復処理できます。
あなたが直面している問題を MVCE で再現し、それを示すことができれば素晴らしいことです。
<ブロック引用>上記の 2 つのメモリの問題が原因であることを確認するにはどうすればよいですか (ツール/メトリックはありますか?)
ここでの情報を踏まえて、プロファイラーを使用することをお勧めします - gprof (-g -pg でコンパイル) が基本的なものです。 Intel コンパイラが利用可能な場合は、vtune を使用できます。
vtune には無料版がありますが、個人的には商用版しか使用していません。
これに加えて、コードにタイミングを挿入できます。テキストの説明からは、マップを作成する時間がマップを消去するのに必要な時間に匹敵するのか、それとも同時に実行すると一貫して増加するのかは明らかではありません。私はifから始めます。 malloc() の現在のバージョンは、並行性のためにも大幅に最適化されていることに注意してください (これは Linux ですか? - 質問にタグを追加してください)。
確かにマップを消去すると数百万の free()
があります は std::~string()
によって呼び出されます -しかし、これが問題であるかどうかを確認する必要があります。より良いアプローチ(回答/コメントで多く言及されています)または単一のユニットとして作成/破棄する巨大なメモリブロックに支えられたカスタムアロケーターを使用できます。
出発点として MVCE を提供すると、私または他の人が一貫した回答を提供できるようになります (これはまだ回答ではありませんが、コメントするには長すぎます)
<ブロック引用>明確にするために、このプログラムは意図的に、他のものを同時に割り当てたり解放したりせず、2 つのスレッドしかなく、1 つは削除専用です。
マップ内の各文字列には 1 つ (またはそれ以上) の new
が必要であることに注意してください。 と 1 つの delete
(malloc()
に基づく) と free()
それぞれ)、キーまたは値のいずれかの文字列です。
マップの「値」には何がありますか?
map<string,<set<int>>
があるので 多くの割り当てがあります:map[string].insert(val)
を実行するたびに 新しいキーの場合、コードは暗黙的に malloc()
を呼び出します 文字列とセットの両方。キーが既にマップにある場合でも、セット内の新しい int には、セット内の新しいノードを割り当てる必要があります。
そのため、構造を構築する際に非常に多くの割り当てが発生します。メモリは一方の側で非常に断片化されており、コードは非常に「malloc 集中型」のように見えます。これは、原則として、メモリ コールが枯渇する原因となる可能性があります。
マルチスレッドのメモリ割り当て/割り当て解除
最新のメモリ サブシステムの特徴の 1 つは、マルチコア システム用に最適化されていることです。1 つのスレッドが 1 つのコアにメモリを割り当てる場合、グローバル ロックではなく、スレッド ローカル プールのスレッド ローカル ロックまたはコア ローカル ロックが存在します。 .
これは、あるスレッドが別のスレッドによって割り当てられたメモリを解放する必要がある場合、非ローカル (低速) ロックが関係していることを意味します。
これは、各スレッドが独自のメモリを割り当て/割り当て解除することが最善の方法であることを意味します。原則として、多くの最適化が可能であると述べました より少ない malloc/free 相互作用を必要とするデータ構造を持つコードでは、各スレッドを次のようにすると、コードはメモリ割り当てに関してよりローカルになります:
- 1 ブロックのデータを取得する
map<string,<set<int>>
をビルドする- 解放
そして、このタスクを繰り返し実行する 2 つのスレッドがあります。
注:同時実行エバリュエーターを処理するには十分な RAM が必要ですが、現在は既に 2 つのエバリュエーターを同時に使用しており、ダブル バッファリング スキーム (1 つは充填、1 つはクリーニング) でロードされています。 RAM の消耗が原因でシステムがスワップしていないことは確かですか?
さらに、このアプローチはスケーラブルです。必要な数のスレッドを使用できます。あなたのアプローチでは、2 つのスレッドに制限されていました。1 つは構造を構築し、もう 1 つは構造を破壊します。
最適化
MVCE がなければ、指示を出すのは困難です。今すぐ適用できるかどうかを知っているだけのアイデア:
- セットを、作成時に予約されたソート済みベクターに置き換える
- マップ キーを、等間隔で並べ替えられた文字列のフラット ベクトルに置き換えます
- 文字列キーをフラット ベクトルに順番に格納し、マップのキーを追跡するためにハッシュを追加します。ベクター内の文字列の順序を追跡するために、ハッシュ マップを追加します。