WPFの画像オブジェクトのWriteableBitmapの塗りつぶしルーチンを自前で作成しました。
UnsafeとParallel.Forの組み合わせで高速化するか確認してみたいと思います。
ソースコード
ファイル名:WirteBitmapBench.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
Unsafeを許可する為にcsprojで以下の設定を行っています。
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
ファイル名:Program.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
internal class Program
{
[STAThread]
private static void Main()
{
int width = 8000;
int height = 8000;
var tests = new (string name, Action<WriteableBitmap> action)[]
{
("Safe", FillGradientSafe),
("SafeParallel", FillGradientSafeParallel),
("Unsafe", FillGradientUnsafe),
("UnsafeParallel", FillGradientUnsafeParallel),
};
// ★ 10回平均
int N = 10;
Console.WriteLine($"Resolution = {width} x {height}");
Console.WriteLine($"Run Count = {N}");
var rng = new Random();
foreach (var t in tests)
{
Console.WriteLine($"--- Warm-up: {t.name} ---");
// ★ ウォームアップ(結果捨て)
var warmupWb = CreateBitmap(width, height);
t.action(warmupWb);
// ★ GCクリーンアップ(安定化)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
var results = tests.Select(test =>
{
long total = 0;
for (int i = 0; i < N; i++)
{
// ★ ランダム順序にしたい場合はここでシャッフル(省略可)
var wb = CreateBitmap(width, height);
var sw = Stopwatch.StartNew();
test.action(wb);
sw.Stop();
total += sw.ElapsedMilliseconds;
// ★ GCクリーンアップ
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
return (test.name, avg: total / (double)N);
});
Console.WriteLine();
Console.WriteLine("=== Result (Average) ===");
foreach (var r in results)
{
Console.WriteLine($"{r.name,-16} : {r.avg:F2} ms");
}
}
private static WriteableBitmap CreateBitmap(int w, int h) =>
new WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, null);
// ---- SAFE ----
private static void FillGradientSafe(WriteableBitmap wb)
{
int width = wb.PixelWidth;
int height = wb.PixelHeight;
int stride = wb.BackBufferStride;
int bytes = stride * height;
byte[] buffer = new byte[bytes];
wb.CopyPixels(buffer, stride, 0);
for (int y = 0; y < height; y++)
{
int row = y * stride;
byte gy = (byte)(y & 0xFF);
for (int x = 0; x < width; x++)
{
int idx = row + x * 4;
buffer[idx + 0] = (byte)(x & 0xFF);
buffer[idx + 1] = gy;
buffer[idx + 2] = (byte)((x + y) & 0xFF);
buffer[idx + 3] = 255;
}
}
wb.WritePixels(new Int32Rect(0, 0, width, height),
buffer, stride, 0);
}
// ---- SAFE + PARALLEL ----
private static void FillGradientSafeParallel(WriteableBitmap wb)
{
int width = wb.PixelWidth;
int height = wb.PixelHeight;
int stride = wb.BackBufferStride;
int bytes = stride * height;
byte[] buffer = new byte[bytes];
wb.CopyPixels(buffer, stride, 0);
Parallel.For(0, height, y =>
{
int row = y * stride;
byte gy = (byte)(y & 0xFF);
for (int x = 0; x < width; x++)
{
int idx = row + x * 4;
buffer[idx + 0] = (byte)(x & 0xFF);
buffer[idx + 1] = gy;
buffer[idx + 2] = (byte)((x + y) & 0xFF);
buffer[idx + 3] = 255;
}
});
wb.WritePixels(new Int32Rect(0, 0, width, height),
buffer, stride, 0);
}
// ---- UNSAFE ----
private static unsafe void FillGradientUnsafe(WriteableBitmap wb)
{
wb.Lock();
try
{
int width = wb.PixelWidth;
int height = wb.PixelHeight;
int stride = wb.BackBufferStride;
byte* basePtr = (byte*)wb.BackBuffer;
for (int y = 0; y < height; y++)
{
byte* row = basePtr + y * stride;
byte gy = (byte)(y & 0xFF);
for (int x = 0; x < width; x++)
{
byte* p = row + x * 4;
p[0] = (byte)(x & 0xFF);
p[1] = gy;
p[2] = (byte)((x + y) & 0xFF);
p[3] = 255;
}
}
wb.AddDirtyRect(new Int32Rect(0, 0, width, height));
}
finally
{
wb.Unlock();
}
}
// ---- UNSAFE + PARALLEL ----
private static void FillGradientUnsafeParallel(WriteableBitmap wb)
{
wb.Lock();
try
{
int width = wb.PixelWidth;
int height = wb.PixelHeight;
int stride = wb.BackBufferStride;
IntPtr buffer = wb.BackBuffer;
Parallel.For(0, height, y =>
{
unsafe
{
byte* basePtr = (byte*)buffer;
byte* row = basePtr + y * stride;
byte gy = (byte)(y & 0xFF);
for (int x = 0; x < width; x++)
{
byte* p = row + x * 4;
p[0] = (byte)(x & 0xFF);
p[1] = gy;
p[2] = (byte)((x + y) & 0xFF);
p[3] = 255;
}
}
});
wb.AddDirtyRect(new Int32Rect(0, 0, width, height));
}
finally
{
wb.Unlock();
}
}
}

コメント