プロジェクトを開始する前に、(System.Collections.Concurrent) からの ConcurrentBag のパフォーマンスをロックとリストと比較する簡単なテストを作成しました。ConcurrentBag が単純な List でロックするよりも 10 倍以上遅いことに非常に驚いています。私が理解していることから、ConcurrentBag は、リーダーとライターが同じスレッドである場合に最適に機能します。しかし、従来のロックよりもパフォーマンスが大幅に低下するとは思いませんでした。
リスト/バッグへの書き込みと読み取りを行う 2 つの Parallel for ループでテストを実行しました。ただし、書き込み自体には大きな違いがあります。
private static void ConcurrentBagTest()
{
int collSize = 10000000;
Stopwatch stopWatch = new Stopwatch();
ConcurrentBag<int> bag1 = new ConcurrentBag<int>();
stopWatch.Start();
Parallel.For(0, collSize, delegate(int i)
{
bag1.Add(i);
});
stopWatch.Stop();
Console.WriteLine("Elapsed Time = {0}",
stopWatch.Elapsed.TotalSeconds);
}
私のボックスでは、このコードの 0.5 ~ 0.9 秒と比較して、実行に 3 ~ 4 秒かかります。
private static void LockCollTest()
{
int collSize = 10000000;
object list1_lock=new object();
List<int> lst1 = new List<int>(collSize);
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Parallel.For(0, collSize, delegate(int i)
{
lock(list1_lock)
{
lst1.Add(i);
}
});
stopWatch.Stop();
Console.WriteLine("Elapsed = {0}",
stopWatch.Elapsed.TotalSeconds);
}
前述したように、読み取りと書き込みを同時に行っても、同時バッグ テストには役立ちません。私は何か間違ったことをしていますか、それともこのデータ構造は本当に遅いですか?
[編集] - ここでは必要ないため、タスクを削除しました (完全なコードには別のタスクの読み取りがありました)
[編集] 回答ありがとうございます。いくつかの答えが混在しているように見えるので、「正しい答え」を選ぶのに苦労しています。
Michael Goldshteyn が指摘したように、速度は実際にはデータに依存します。Darin は、ConcurrentBag を高速化するにはもっと多くの競合が必要であり、Parallel.For は必ずしも同じ数のスレッドを開始するとは限らないと指摘しました。覚えておくべき 1 つのポイントは、ロック内で必要のないことは何もしないことです。上記の場合、一時変数に値を代入している可能性を除いて、ロック内で何もしていないように見えます。
さらに、sixlettervariables は、たまたま実行されているスレッドの数も結果に影響を与える可能性があることを指摘しましたが、元のテストを逆の順序で実行してみましたが、ConcurrentBag は依然として低速でした。
15 個のタスクを開始していくつかのテストを実行しましたが、結果は特にコレクションのサイズに依存していました。ただし、ConcurrentBag は、最大 100 万回の挿入で、リストをロックするのとほぼ同じかそれ以上のパフォーマンスを発揮しました。100 万を超えると、ロックがはるかに高速になることがあるように見えましたが、私のプロジェクトでこれより大きなデータ構造を使用することはおそらくないでしょう。実行したコードは次のとおりです。
int collSize = 1000000;
object list1_lock=new object();
List<int> lst1 = new List<int>();
ConcurrentBag<int> concBag = new ConcurrentBag<int>();
int numTasks = 15;
int i = 0;
Stopwatch sWatch = new Stopwatch();
sWatch.Start();
//First, try locks
Task.WaitAll(Enumerable.Range(1, numTasks)
.Select(x => Task.Factory.StartNew(() =>
{
for (i = 0; i < collSize / numTasks; i++)
{
lock (list1_lock)
{
lst1.Add(x);
}
}
})).ToArray());
sWatch.Stop();
Console.WriteLine("lock test. Elapsed = {0}",
sWatch.Elapsed.TotalSeconds);
// now try concurrentBag
sWatch.Restart();
Task.WaitAll(Enumerable.Range(1, numTasks).
Select(x => Task.Factory.StartNew(() =>
{
for (i = 0; i < collSize / numTasks; i++)
{
concBag.Add(x);
}
})).ToArray());
sWatch.Stop();
Console.WriteLine("Conc Bag test. Elapsed = {0}",
sWatch.Elapsed.TotalSeconds);