目的
1回1回の処理が重い列挙を速くしたい。
こんなの。
var source = Enumerable.Range(0, 20);
var sw = Stopwatch.StartNew();
foreach (var i in source) {
Fetch(i);
}
Console.WriteLine(sw.Elapsed);
// 00:00:02.0102643
void Fetch(int number) {
// 重い処理。
Thread.Sleep(100);
}
並列実行したら速そう。
AsParallel() を付ける
foreach (var i in source.AsParallel()) {
Fetch(i);
}
Console.WriteLine(sw.Elapsed);
// 00:00:02.0139477
あれ、遅い・・・
foreach → ForAll() で列挙
source.AsParallel().ForAll(i => {
Fetch(i);
});
Console.WriteLine(sw.Elapsed);
// 00:00:00.5032450
(サラマンダーより)速い!
Parallel.ForEach()
も試してみる。
Parallel.ForEach(source, i => {
Fetch(i);
});
Console.WriteLine(sw.Elapsed);
// 00:00:00.5017158
速い!
比較
BenchmarkDotNet はマルチスレッドのベンチマーク向けにデザインされていないけど、一応貼っておく。
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.14393.2068 (1607/AnniversaryUpdate/Redstone1)
Intel Core i5-6200U CPU 2.30GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
Frequency=2343751 Hz, Resolution=426.6665 ns, Timer=TSC
.NET Core SDK=2.1.104
[Host] : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT
Core : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT
Job=Core Runtime=Core
Method | Mean | Error | StdDev | Median | Min | Max | Scaled | ScaledSD | Gen 0 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
Enumerable_foreach | 36.781 ms | 0.7284 ms | 0.6457 ms | 36.771 ms | 35.680 ms | 37.995 ms | 1.00 | 0.00 | - | 0 B |
ParallelQuery_foreach | 36.969 ms | 0.6525 ms | 0.6104 ms | 36.757 ms | 35.833 ms | 38.051 ms | 1.01 | 0.02 | - | 0 B |
ParallelQuery_ForAll | 9.753 ms | 0.0826 ms | 0.0848 ms | 9.764 ms | 9.579 ms | 9.885 ms | 0.27 | 0.01 | - | 3808 B |
Parallel_ForEach | 2.975 ms | 0.3545 ms | 1.0453 ms | 2.503 ms | 1.974 ms | 5.392 ms | 0.08 | 0.03 | 31.2500 | 5959 B |
ParallelQuery_ForAll の Scaled
が Enumerable_foreach(ベースライン)の 1/4 になっていて、4つの論理コアをいい感じに使っている(Parallel_ForEach はなんかアレだけど)。
ソースコード
総括
ParallelQuery
を foreach で列挙しても foreach 自体は並列実行されない ので、列挙の並列化が必要な時は ForAll()
を使いましょう。