私は.NET 4.5 Single Instance WCFサービスを持っています。これはリスト内のアイテムのコレクションを同時に保持し、同時にリーダーとライターを持っていますが、ライターよりもはるかに多くのリーダーを持っています。
現在、BCL を使用するかConcurrentBag<T>
、独自のカスタム汎用ThreadSafeListクラスを使用するかを決定しています (BCL を拡張IList<T>
およびカプセル化するのReaderWriterLockSlim
は、複数の同時読み取りにより適しているためです)。
100 万人のリーダー (Sum Linq クエリを実行するだけ) と 100 人のライター (リストに項目を追加する) の同時シナリオをシミュレートして、これらの実装をテストしたところ、多数のパフォーマンスの違いが見つかりました。
私のパフォーマンス テストでは、タスクのリストがあります。
List<Task> tasks = new List<Task>();
テスト 1: 次のコードを使用して、100 万個のリーダー タスクに続いて 100 個のライター タスクを作成した場合:
tasks.AddRange(Enumerable.Range(0, 1000000).Select(n => new Task(() => { temp.Where(t => t < 1000).Sum(); })).ToArray());
tasks.AddRange(Enumerable.Range(0, 100).Select(n => new Task(() => { temp.Add(n); })).ToArray());
次のタイミング結果が得られます。
- コンカレントバッグ: ~300ms
- スレッドセーフリスト: ~520ms
テスト 2: ただし、100 個のライター タスクと混合された 100 万のリーダー タスクを作成すると (これにより、実行されるタスクのリストは {Reader,Reader,Writer,Reader,Reader,Writer etc} になります。
foreach (var item in Enumerable.Range(0, 1000000))
{
tasks.Add(new Task(() => temp.Where(t => t < 1000).Sum()));
if (item % 10000 == 0)
tasks.Add(new Task(() => temp.Add(item)));
}
次のタイミング結果が得られます。
- コンカレントバッグ: ~4000ms
- スレッドセーフリスト: ~800ms
各テストの実行時間を取得するための私のコードは次のとおりです。
Stopwatch watch = new Stopwatch();
watch.Start();
tasks.ForEach(task => task.Start());
Task.WaitAll(tasks.ToArray());
watch.Stop();
Console.WriteLine("Time: {0}ms", watch.Elapsed.TotalMilliseconds);
カスタム実装よりも、テスト 1 の ConcurrentBag の効率が良く、テスト 2 の ConcurrentBag の効率が悪いため、どちらを使用するかを判断するのは難しいと思います。
Q1. リスト内のタスクの順序だけを変更しているのに、結果がこれほど異なるのはなぜですか?
Q2. テストをより公平にするためにテストを変更するより良い方法はありますか?