C#でforeachとParallel.ForEachで処理時間を比較してみた

C# コンピュータ
C#

176枚のPNGファイル(ファイルサイズ合計:4.66GB)を読み込みBitmapとしてDictionaryにセットするプログラムで、普通のforeachとParallel.ForEachで速度を計測してみました。
CPUは6コア12スレッドのRyzen5 5600Xです。

ソースコード

class Program1
{
    static bool parallelFlag = true; // false...通常のforeach
    static Dictionary<string, System.Drawing.Image> Imgs = new Dictionary<string, System.Drawing.Image>();
    static Object LockObject = new Object();
    public static void Main(string[] args)    
    {
        var path = @"C:\sample";

        var files = System.IO.Directory.EnumerateFiles(path, "*.png", System.IO.SearchOption.AllDirectories);

        DateTime startTime;
        DateTime endTime;

        Imgs.Clear();
        startTime = DateTime.Now;

        Action<string> func = (filename)=>
        {
            if (!Imgs.ContainsKey(filename))
            {
                using var fs = new System.IO.FileStream(filename, FileMode.Open, FileAccess.Read);
                var bmp = System.Drawing.Bitmap.FromStream(fs);
                lock(LockObject)
                {
                    Imgs.Add(filename, bmp);
                }
            }            
        };

        if (parallelFlag == true)
        {
            Parallel.ForEach(files, file =>
            {
                func(file);
            });
        }
        else
        {
            foreach(var file in files)
            {
                func(file);
            }
        }
        endTime = DateTime.Now;
        Console.WriteLine("{0}:{1}ms", parallelFlag ? "Parallel.ForEach" : "foreach", (endTime - startTime).TotalMilliseconds);
    }
}

実行

普通のforeach

メモリはどんどん消費されて順調に動作はしていますがCPU使用率は穏やか。


結果

foreach:61986.2507ms

Parallel.ForEach(Ryzen5 5600X 6コア12スレッド)

数秒間だけですが、すべてのコアに負荷がかかりグラフに山が出来ています。


結果

Parallel.ForEach:7099.3267ms

Parallel.ForEach(Ryzen5 5600X 6コア6スレッド)

UEFIでSMTをOFFにして6コア6スレッドにしてみました。

結果

Parallel.ForEach:12913.4134ms

 

感想

普通のforeachが1分ぐらいでParallel.ForEachが7秒ほどですので、結構速度差が出ました。
試しにSMTをOFFにしてスレッド数を半分にしたところ処理時間は13秒ほどでした。
並列処理はコアがたくさんあったほうがパフォーマンスが良くなることが、なんとなく体感できました。
もっとたくさんコアが乗ったCPUとメモリが欲しくなりますが、パフォーマンスが発揮できるような並列処理のコードが自分で書けるかというと、難しそうです。

処理時間は短くなりましたが、ストレージのアクセスを含めているため、PC自体の動作が極端に遅くなります。アプリケーションに組み込むには難があります。そもそも画像ビューアで表示する画像をキャッシュに先読みさせる機能の試作でしたが、並列処理の場合順不同に処理されるので全ての処理の終了を待つ必要があるため、処理時間は短くなりましたが操作性は悪くなってしまいました。

ファイルの読み込みが問題だとすると、フィルの読み込みは普通のforeachでbitmapオブジェクトの生成をParallel.ForEachで試してみました。残念ながらトータルの処理時間が遅く、別スレッドで普通のforeachで処理する方が処理時間が速い結果になります。筆者の知識では並列処理で効果が出るかどうかは試してみないと何とも言えない感じです。

追記:20240415
Paralll.ForEachの引数にオプションが設定できて、そのオプションのParallelOptions.MaxDegreeOfParallelismプロパティにCPUのコア数を指標とした適当な数値をセットすると、CPUの使用率がマイルドになります。

コメント