最初に、次のようなマイクロ ベンチマークに関するいくつかの一般的な注意事項を示します。
- ここでのタイミングは非常に短いため、JIT 時間が重要になる可能性があります。並列ループには、最初に呼び出されたときにのみ JIT される匿名のデリゲートが含まれているため、これは重要です。
ForEach
そのため、JIT 時間は、ベンチマークが初めて実行されるタイミングに含まれます。
- コードのコンテキストも重要です。JITter は、小さな関数を最適化することで、より良い仕事をすることができます。ベンチマーク コードを独自の関数に分離すると、パフォーマンスに大きな影響を与える可能性があります。
コードを高速化するための 4 つの基本的な手法があります (純粋な CLR を維持する場合)。
- それを並列化します。これは明らかです。
- ループを展開します。これにより、2 回以上の繰り返しごとに比較を行うだけで、命令の数が削減されます。
- 安全でないコードの使用。この場合、主な問題 (アレイの範囲チェック) が最適化されているため、これはあまりメリットがありません。
- コードをより最適化できるようにします。これを行うには、実際のベンチマーク コードを別のメソッドに配置します。
並列コードは次のとおりです。
var syncObj = new object();
Parallel.ForEach(Partitioner.Create(0, array.Length),
() => 0,
(src, state, partialSum) => {
int end = src.Item2;
for (int i = src.Item1; i < end; i++)
partialSum += array[i];
return partialSum;
},
partialSum => { lock (syncObj) { s += partialSum; } });
Partitioner
クラスは名前System.Collections.Concurrent
空間に存在します。
私のマシン (i7 950、8 つの論理コア) では、得られたタイミングは次のとおりです。
For loop: 196.786 ms
For loop (separate method): 72.319 ms
Unrolled for loop: 196.167 ms
Unrolled for loop (separate method): 67.961 ms
Parallel.Foreach (1st time): 48.243 ms
Parallel.Foreach (2nd time): 26.356 ms
32 ビット コードと 64 ビット コードの間に大きな違いはありませんでした。