【C#4.0~】Parallel.Forによる並列処理

C#4.0(Visual Studio 2010)からはParallel.For(名前空間:System.Threading.Tasks)による並列処理が可能となります。

2コアや4コアは当たり前の時代なので、C#のParallel.Forを使った方が良いかは別としても、並列処理はしないとCPUの無駄遣い状態になってしまいます。

Parallel.Forの構文については、定番の下記 ++C++のページ↓

https://ufcpp.net/study/csharp/lib_parallel.html#parallel

を見て頂くと分かりやすいかと思いますが、通常のfor文では

for (int i = 0; i < N; i++)
{
    // 通常の処理
}

と書くところを

Parallel.For(0, N, i =>
{
    // この部分の処理が並列化される
});

のように書くと、Parallel.Forの{ }内では並列処理が行われます。

ここでは、Parallel.Forを画像処理的に使うと、どうなるか?やってみます。

まず、通常のfor文でコントラスト調整をする以下のようなメソッドを作ります。

private void ContrastImage(Bitmap bmp, double scale, double offset)
{
    var width = bmp.Width;
    var height = bmp.Height;

    // Bitmapをロック
    var bmpData = bmp.LockBits(
            new Rectangle(0, 0, width, height),
            System.Drawing.Imaging.ImageLockMode.ReadWrite,
            bmp.PixelFormat
        );

    // メモリの幅のバイト数を取得
    var stride = Math.Abs(bmpData.Stride);

    int channel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;

    // 画像データ格納用配列
    var picData = new byte[stride * bmpData.Height];

    // Bitmapデータを配列へコピー
    System.Runtime.InteropServices.Marshal.Copy(
        bmpData.Scan0,
        picData,
        0,
        stride * bmpData.Height
        );

    for (int y = 0; y < height; y++)
    {
        int lineIndex = stride * y;

        for (int x = 0; x < width * channel; x++)
        {
            int value = picData[lineIndex + x];
            value = (int)(value * scale + offset);
            value = (value < 0) ? 0 : (value > 255) ? 255
                    : value
                    ;
            picData[lineIndex + x] = (byte)value;
        }
    }

    // 配列をBitmapデータへコピー
    System.Runtime.InteropServices.Marshal.Copy(
        picData,
        0,
        bmpData.Scan0,
        stride * bmpData.Height
        );

    // アンロック
    bmp.UnlockBits(bmpData);
}

このメソッドの二重ループの部分

for (int y = 0; y<height; y++)
{
    int lineIndex = stride * y;

    for (int x = 0; x<width* channel; x++)
    {
        int value = picData[lineIndex + x];
        value = (int)(value* scale + offset);
        value = (value< 0) ? 0 : (value > 255) ? 255
                : value
                ;
        picData[lineIndex + x] = (byte)value;
    }
}

をParallel.Forを使って並列処理にすると

Parallel.For(0, height, y =>
{
    int lineIndex = stride * y;

    for (int x = 0; x<width* channel; x++)
    {
        int value = picData[lineIndex + x];
        value = (int)(value* scale + offset);
        value = (value< 0) ? 0 : (value > 255) ? 255
                : value
                ;
        picData[lineIndex + x] = (byte)value;
    }
}
);

となります。

実際に、このメソッドを使って処理を行い、5回の平均処理時間を比べたところ

通常のfor文 Parallel.For
197.2msec 92.4msec

CPU:i7-7700K@4.2GHz(4コア)

処理画像サイズ:6000 x 40000 x 24bitカラー

という結果になりました。

評価ソフトのイメージ

(処理前) (処理後)
C# Parallel.For C# Parallel.For

Parallel.Forを使うとCPUのコア数分ぐらいは速くなるのか?と期待していたのですが、他の処理を行っても、だいたい2倍ちょっと速くなるようです。

また、Parallel.Forを使い始めたばかりだとやってしまいがちな定番のミスですが、
ここでのサンプルの lineIndexvalue をParallel.Forよりも前で定義してしまうと、正しい結果を得られなくなるので、ご注意下さい。

← C#2.0からの脱却 へ戻る

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

関連記事

スポンサーリンク