木をすばやく横断する必要があり、並行して実行したいと思います。スレッドの束を手動でスピンアップするよりも、並列拡張機能を使用したいと思います。
私の現在のコードは次のようになります。
public void Traverse(Node root)
{
var nodeQueue = new Queue<Node>();
nodeQueue.Enqueue(root);
while (nodeQueue.Count!=0)
{
var node = nodeQueue.Dequeue();
if (node.Property = someValue) DoSomething(node);
foreach (var node in node.Children)
{
nodeQueue.Enqueue(node);
}
}
}
Parallel.ForEachにParallel.Whileアナログがあることを本当に望んでいました。Parallel.ForEachを使用したParallelの実装に関するStephenToubの記事に出くわしました。正しく読み取った場合、反復しようとしているキューを変更しているため、これはまだ機能しません。
タスクファクトリと再帰を使用する必要がありますか(そしてそれは危険ですか?)?または私が見落としているいくつかの簡単な解決策はありますか?
編集:@svick
ツリーには250,000を超えるノードがあります。現在の最大深度は、ルートを含めて14ノードです。
ルートから約500のノードがあり、その後のバランスはかなりランダムに分布しています。私はすぐに分布に関するいくつかのより良い統計を得るでしょう。
@Enigmativity:
はい、ツリーは多くのユーザーによって同時に変更されていますが、通常、ツリーまたはサブツリーの共有読み取りロックを使用するか、ダーティ読み取りを許可します。
node.Childrenへの呼び出しはアトミックと見なすことができます。
DoSomethingは、実際にはいくつかのデリゲートの1つです。一部の高価な操作では、ノードのスナップショットリストを収集し、トラバーサルの外部で処理します。
おそらく一般的なケース(ツリー全体ではなくサブツリーがトラバースされている)を確認する必要があることに気付きました。そのために、ツリーのすべてのノードでトラバースを実行し、合計時間を確認しました。
各トラバーサルアルゴリズムにParallel.ForEach(nodes、Traverse)を使用しました。ここで、ノードには約25万個のノードがすべて含まれていました。これは、多くの異なるノードを同時に要求する多くのユーザーをシミュレートした(一種の)ものです。
00256ms幅優先シーケンシャル
00323ms幅優先探索(作業あり)(静的カウンターを「作業」としてインクリメントしました)
01495msカークス最初の答え
01143msSvicks2番目の回答
00000ms再帰シングルスレッドは60秒後に終了しませんでした
00000ms謎の答えは60秒後に終了しませんでした
@エニグマ、私はあなたのブログをどうにかして台無しにした可能性があると思います。
結果は控えめに言っても私を驚かせた。コンパイラーがトラバーサルを魔法のように最適化していないことを確信するために、幅優先探索にいくつかの作業を追加する必要がありました。
頭を1回トラバースする場合、最初のレベルを並列化すると最高のパフォーマンスしか得られませんでした。しかし、ほんのわずかに、この数は、第2レベルにノードを追加したときに改善されました(500ではなく2000)。