for文とParallel.Forで処理時間を比較してみたいと思います。
プロジェクトの作成
cd (mkdir ConvertToGray)
dotnet new console -f net8.0
ソースコード
ファイル名:ConvertToGray.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
ファイル名:WpfBitmapUtility.cs
// WPFのBitmap関連
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace CommonLib;
public static class WpfBitmapUtility
{
public static BitmapSource LoadBitmap(string path)
{
using FileStream fs = new(path, FileMode.Open, FileAccess.Read);
var decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
var frame = decoder.Frames[0];
return new FormatConvertedBitmap(frame, PixelFormats.Bgra32, null, 0); // 32bpp固定
}
public static void SaveBitmap(BitmapSource bitmap, string path)
{
using FileStream fs = new(path, FileMode.Create);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(fs);
}
public static BitmapSource ConvertToGray(BitmapSource source, bool useParallel)
{
int width = source.PixelWidth;
int height = source.PixelHeight;
int stride = width * 4;
byte[] pixels = new byte[height * stride];
source.CopyPixels(pixels, stride, 0);
Action<int> processLine = y =>
{
int offset = y * stride;
for (int x = 0; x < width; x++)
{
int i = offset + x * 4;
byte b = pixels[i + 0];
byte g = pixels[i + 1];
byte r = pixels[i + 2];
byte gray = (byte)((r * 0.299 + g * 0.587 + b * 0.114));
pixels[i + 0] = gray;
pixels[i + 1] = gray;
pixels[i + 2] = gray;
// Alphaはそのまま
}
};
if (useParallel)
Parallel.For(0, height, processLine);
else
for (int y = 0; y < height; y++) processLine(y);
return BitmapSource.Create(width, height, source.DpiX, source.DpiY, PixelFormats.Bgra32, null, pixels, stride);
}
}
ファイル名:Program.cs
// コンソールアプリのエントリポイント
using System.Diagnostics;
using System.Windows.Media.Imaging;
using CommonLib;
// namespace ;
class Program
{
static void Main()
{
string inputPath = @"J:\testdata\00001un.png";
string outputPath1 = "./gray_for.png";
string outputPath2 = "./gray_parallel.png";
var sw = Stopwatch.StartNew();
BitmapSource source = WpfBitmapUtility.LoadBitmap(inputPath);
Console.WriteLine("=== グレースケール変換(for)===");
sw.Restart();
BitmapSource gray1 = WpfBitmapUtility.ConvertToGray(source, useParallel: false);
sw.Stop();
Console.WriteLine($"for時間: {sw.ElapsedMilliseconds}ms");
WpfBitmapUtility.SaveBitmap(gray1, outputPath1);
Console.WriteLine("=== グレースケール変換(Parallel.For)===");
sw.Restart();
BitmapSource gray2 = WpfBitmapUtility.ConvertToGray(source, useParallel: true);
sw.Stop();
Console.WriteLine($"Parallel.For時間: {sw.ElapsedMilliseconds}ms");
WpfBitmapUtility.SaveBitmap(gray2, outputPath2);
}
}
実行結果
dotnet run
#=== グレースケール変換(for)===
#for時間: 201ms
#=== グレースケール変換(Parallel.For)===
#Parallel.For時間: 131ms
通常のforが201msに対し並列のParallel.Forが131msで65%程度に処理時間が短縮されました。
画像の解像度は幅高さは4512×6400でRGBAの32bit
実行したPCはRyzen7 5700X 8コア16スレッド
コメント