ループ内で時間がかかる処理を行う必要があり、処理の高速化を行いました。その時の知見を残します。
概要
並列処理を実装するためには大きく分けて2つの方法が存在します。
-
Thread
を使用して別スレッドに分ける。 -
Parallel
を使用してループを並列で実行する。
前者はより細かく制御できますが設定が煩雑です。後者は処理が重いループのみ並列化が可能なためとても手軽です。
今回はParallel
を使用した方法で確認します。
確認方法
対象処理
並列化の対象関数は以下のような処理とします。
ループ処理がネストしており、それぞれ 10 回と 1000000000 回と回数に大きく差があります。
想定としては「地区に小学校が 10 校存在し、それぞれ 2000 人ずつ生徒が在籍している」みたいな感じです。
並列処理の効果は以下の 4 パターンで確認します。
Loop A | Loop B |
---|---|
for | for |
for | Parallel.For |
Parallel.For | for |
Parallel.For | Parallel.For |
測定方法
測定用のメソッドを作成しました。対象のメソッドと測定回数を渡すと処理の平均時間を出力してくれます。
// 引数で渡されたメソッドの実行時間を計測する
static void MeasureTime(Action action, int times)
{
int time = 0;
for (int i = 0; i < times; i++)
{
time += MeasureTime(action);
}
Console.WriteLine("Average Time taken: {0}ms", time / times);
Console.WriteLine();
static int MeasureTime(Action action)
{
var sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);
return (int)sw.Elapsed.TotalMilliseconds;
}
}
結果
Loop A | Loop B | 処理時間 |
---|---|---|
for | for | 13076ms |
for | Parallel.For | 3948ms |
Parallel.For | for | 9237ms |
Parallel.For | Parallel.For | 27253ms |
上記のような結果になりました。基本的にはループ回数が多いところを並列化すると効果が出るような結果になりました。
また、2 つのループを両方並列化した場合の結果を見ると速度が向上していないことも興味深いです。処理時間を見ると最短では 9000ms ほどで完了しているケースもあり、ばらつきが大きいようにも見えました。集計回数を3で実行しましたが、もっと大きくするともう少し小さい値に収束しそうな感じがします。
実際に両方とも並列化したケースで回数を 15 回で実行したところ処理時間の平均は 17481ms になりました。タスクマネージャーを見ると CPU、メモリともに 100%近い値になっており分割しすぎてリソースが枯渇して逆に遅くなっていたようです。
また、同じような機能のForeach
も同じような結果になるかは要検証です。
おわりに
今回の実験から、並列処理が必ずしも処理速度の向上に繋がるわけではないことがわかりました。特に、リソースが限られている環境では、並列化が逆効果になることもあります。並列処理を導入する際には、実際の環境でのパフォーマンスをしっかりと測定し、最適な方法を選択することが重要です。
次回は、Foreach を使った並列処理のパフォーマンスについても検証してみたいと思います。お楽しみに!
参考
実行したコードは下記です。