C# .NET FrameworkでもCPUの拡張命令SIMDと並列処理で、画像の減色処理速度40倍
2020/03追記
処理速度40倍ってあるけど、実際はそんなに速くなっていない可能性
MathクラスのPowを使わないで普通に掛け算すれば、30~80倍くらい速くなる。逆に言うとPowを使うと遅くなる。SIMDでの処理ではMathPowを使っていないから、それもあって40倍も速くなったのかも。
追記ここまで
実際の画像処理を並列処理とSIMDを使ってどこまで速くなるのか
こちらを参考にして書いてみた
ランダムな16色に減色するアプリ
それぞれの処理時間を表示する
画像ファイルドロップで表示
画像を左クリック押しっぱなしで減色前の画像表示
保存ボタンでファイルに保存
アプリダウンロード
ギットハブ
使った画像はスマカメで撮った3264x2448のjpegファイル、これをランダムな16色に減色
結果
こんなに速くなるとは思わなかった
16/0.4=40倍!
使っているCPUのRyzen 5 2400Gは、物理4コア、論理8コア(スレッド)
電源の設定は
CPUの電源管理は90~100%になっていた、アクティブってだけある
クロックは色々起動した状態で3GHz前後
シングルスレッドからマルチスレッドで4倍速
1番2番はほとんど同じ処理で普通のForを使ったシングルスレッド
3~5番は1,2番をParallelクラスとTaskクラスを使ってマルチスレッドにした感じ
比較するとだいたい4倍くらい速くなっている、スレッド数は論理コア数と同じ8スレッドでの処理をしているはずなんだけど8倍速にはならなかった、論理コアは気休め程度ってことかしら
SIMDの使用で10倍速
1番にSIMDを使うようにしたのが6番
16/1.6=10倍!
色の距離の計算のところでSIMDを使うVectorクラスを使うようにしただけで10倍も速くなった!SIMDすごい
マルチスレッド+SIMDで40倍速
1番をマルチスレッド化+SIMDにしたのが9番
16/0.4=40倍!!!
いままでのマルチで4倍とSIMDで10倍になったんだから、単純に合わせて40倍になる?って本当に40倍速になるのがすごいw使い所がかっちり決まるとこんなに速くなるんだなあ
7,8番もマルチ+SIMDなんだけど、Parallelクラスでマルチスレッド化するときに、うまく書くことができなくて余計な処理が入っているので遅くなっている
CPU使用率
3番だと100%使い切っているのがわかる、9番は0.4秒で終わるから100%表示にならない、1番はシングルスレッドなので1/8=0.125、12.5%
クロックは1番だと最大で3.4GHz、3番は最大で3.7GHz、9番は一瞬だけ3.4GHz
.NET FrameworkでSIMDを使うには設定が必要(.NET Coreなら設定しなくても使えるみたい)
メニューのツールから、NuGetパッケージマネージャー→ソリューションのNuGetパッケージの管理
参照を選択して
vectorsで検索
検索結果からSystem.Numerics.Vectorsを選択して右上の
プロジェクトにチェック入れてインストール
確認画面出るからおk押して完了
出力ウィンドウでも確認できる、これで
参照に2つ追加される
System.Numerics
System.Numerics.Vectors
準備できた
usingにSystem.Numericsを加えたりしておけば
vecとタイプしただけでSIMDを使うVectorクラスが候補に出てくる
色の距離はいろいろな方法があるけど、今回も一番単純なユークリッド距離を使った。単純でも計算は平方根を使うから重いみたいなので、これをSIMDにおまかせ。これだけで10倍速!
SIMDを使わないMathクラスで普通にユークリッド距離の計算、平方根を求めるSqrtが重いんだろうねえ
SIMD、CPU拡張命令
システムモニタ系のアプリで見ることができるCPUの詳細で、Featuresのところ
SIMDを使うってのはここの拡張命令を使うってことみたい
RyzenではAVX系に対応したことで画像処理や動画エンコードが速くなったってのを聞いていたから、今回のもそれが大きいのかも
比較
コードは
小ネタ 並列化 | ++C++; // 未確認飛行 C ブログ
こちらのをコピペ
singleがシングルスレッド、multiがマルチスレッド
4コア8スレッドのRyzen 5 2400Gのマルチスレッド化による速度アップは2.24倍と1.37倍、PhenomⅡ X3は3コア3スレッドで2.03倍と2.06倍だから、このベンチマークだとRyzenの伸びはいまいちな感じ
SIMDの効果はシングルスレッドなら1.9倍でPhenomよりは伸びたけど、画像処理での伸び率を見た後だと、もっと伸びてもいいような感じがする。どんな関数を使うかによって変わってくるのかしらねえ
Phenomでも今回の画像処理アプリを動かせればいいんだけど、Windows 10のライセンスはRyzenのPCに移しちゃったから動かせない
今回のアプリのコード
<Window x:Class="_20200117gensyoku.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20200117gensyoku" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="200"/> </Grid.ColumnDefinitions> <ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" UseLayoutRounding="True"> <Grid x:Name="MyGrid" PreviewMouseLeftButtonDown="MyGrid_PreviewMouseLeftButtonDown" PreviewMouseLeftButtonUp="MyGrid_PreviewMouseLeftButtonUp"> <Image x:Name="MyImageOrigin" Stretch="None" UseLayoutRounding="True"/> <Image x:Name="MyImage" Stretch="None" UseLayoutRounding="True"/> </Grid> </ScrollViewer> <StackPanel Grid.Column="1"> <StackPanel.Resources> <Style TargetType="Button"> <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="Margin" Value="2,6,2,0"/> </Style> </StackPanel.Resources> <Button Content="保存" Click="ButtonSaveImage_Click"/> <Button Content="1:普通、シングルスレッド" x:Name="ButtonExe1" Click="ButtonExe1_Click"/> <TextBlock x:Name="tbTime1" Text="time"/> <Button Content="2:普通、シングルスレッド" x:Name="ButtonExe2" Click="ButtonExe2_Click"/> <TextBlock x:Name="tbTime2" Text="time"/> <Button Content="3:Parallelでの並列処理(マルチスレッド)" x:Name="ButtonExe3" Click="ButtonExe3_Click"/> <TextBlock x:Name="tbTime3" Text="time"/> <Button Content="4:Taskを使った並列処理、1ピクセルごとのRGBAのByte配列のリスト" x:Name="ButtonExe4" Click="ButtonExe4_Click"/> <TextBlock x:Name="tbTime4" Text="time"/> <Button Content="5:Taskを使った並列処理" x:Name="ButtonExe5" Click="ButtonExe5_Click"/> <TextBlock x:Name="tbTime5" Text="time"/> <Button Content="6:シングルスレッド+SIMD" x:Name="ButtonExe6" Click="ButtonExe6_Click"/> <TextBlock x:Name="tbTime6" Text="time"/> <Button Content="7:Parallelでの並列処理+SIMD" x:Name="ButtonExe7" Click="ButtonExe7_Click"/> <TextBlock x:Name="tbTime7" Text="time"/> <Button Content="8:Taskを使った並列処理+SIMD、1ピクセルごとのRGBAのByte配列のリスト" x:Name="ButtonExe8" Click="ButtonExe8_Click"/> <TextBlock x:Name="tbTime8" Text="time"/> <Button Content="9:Taskを使った並列処理+SIMD" x:Name="ButtonExe9" Click="ButtonExe9_Click"/> <TextBlock x:Name="tbTime9" Text="time"/> </StackPanel> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Diagnostics;//ストップウォッチ用 using System.Numerics;//SIMDのVector用 //参照したところ //小ネタ 並列化 | ++C++; // 未確認飛行 C ブログ //https://ufcpp.net/blog/2016/12/tipsparallelism/ namespace _20200117gensyoku { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { BitmapSource OriginBitmapSource; byte[] OriginPixels; string ImageFileFullPath; public MainWindow() { InitializeComponent(); this.AllowDrop = true; this.Drop += MainWindow_Drop; //string path = @"D:\ブログ用\チェック用2\WP_20200111_09_25_36_Pro_2020_01_11_午後わてん.jpg"; //(OriginPixels, OriginBitmapSource) = MakeBitmapSourceAndPixelData(path, PixelFormats.Bgra32, 96, 96); //MyImageOrigin.Source = OriginBitmapSource; } private void MainWindow_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; } string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop); (OriginPixels, OriginBitmapSource) = MakeBitmapSourceAndPixelData(filePath[0], PixelFormats.Bgra32, 96, 96); if (OriginBitmapSource == null) { MessageBox.Show("画像として開くことができなかった"); } else { MyImageOrigin.Source = OriginBitmapSource; MyImage.Source = OriginBitmapSource; ImageFileFullPath = filePath[0]; } } //白、黒、赤、緑、青、黄色、水色、赤紫の固定8色パレット作成 private List<byte[]> MakePalette() { List<byte[]> palette = new List<byte[]> { new byte[] { 255, 255, 255 }, new byte[] { 0, 0, 0 }, new byte[] { 255, 0, 0 }, new byte[] { 0, 255, 0 }, new byte[] { 0, 0, 255 }, new byte[] { 255, 255, 0 }, new byte[] { 0, 255, 255 }, new byte[] { 255, 0, 255 } }; return palette; } //ランダム色パレット作成 private List<byte[]> MakePalette(int count) { byte[] temp = new byte[count * 3]; // Random r = new Random((int)DateTime.Now.Ticks); Random r = new Random(); r.NextBytes(temp); List<byte[]> palette = new List<byte[]>(); for (int i = 0; i < temp.Length; i += 3) { palette.Add(new byte[] { temp[i], temp[i + 1], temp[i + 2] }); } return palette; } //1:普通、シングルスレッド private BitmapSource Zs1減色1(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; for (int i = 0; i < OriginPixels.Length; i += 4) { //パレットの中から、今のPixelの色に一番近い色を取得 double min = ColorDistance(pixels[i + 2], pixels[i + 1], pixels[i], palette[0]); int pIndex = 0; for (int pc = 1; pc < palette.Count; pc++) { var distance = ColorDistance(pixels[i + 2], pixels[i + 1], pixels[i], palette[pc]); if (min > distance) { min = distance; pIndex = pc; } } //一番近い色に置き換える zPixels[i + 3] = 255;//A zPixels[i + 2] = palette[pIndex][0];//R zPixels[i + 1] = palette[pIndex][1];//G zPixels[i] = palette[pIndex][2];//B } return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //2:普通、シングルスレッド private BitmapSource Zs2減色2(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; byte r, g, b; for (int i = 0; i < OriginPixels.Length; i += 4) { r = pixels[i + 2]; g = pixels[i + 1]; b = pixels[i];//1と違うところ double min = ColorDistance(r, g, b, palette[0]); int pIndex = 0; //パレットから一番近い色 for (int pc = 1; pc < palette.Count; pc++) { var distance = ColorDistance(r, g, b, palette[pc]); if (min > distance) { min = distance; pIndex = pc; } } zPixels[i + 3] = 255;//A zPixels[i + 2] = palette[pIndex][0];//R zPixels[i + 1] = palette[pIndex][1];//G zPixels[i] = palette[pIndex][2];//B } return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //3:Parallelでの並列処理 private BitmapSource ZmP減色3(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; List<byte[]> MyList = MakeByteRGBA(pixels);//1ピクセルごとのRGBAのByte配列のリスト Parallel.For(0, MyList.Count, n => { double min = ColorDistance(MyList[n], palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistance(MyList[n], palette[i]); if (min > distance) { min = distance; pIndex = i; } } var p = n * 4; zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; }); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //4:Taskを使った並列処理、1ピクセルごとのRGBAのByte配列のリスト private BitmapSource ZmT1減色4(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; List<byte[]> MyList = MakeByteRGBA(pixels);//1ピクセルごとのRGBAのByte配列のリスト int MyThread = Environment.ProcessorCount; int windowSize = MyList.Count / MyThread;//1スレッドに割り当てる量 Task.WhenAll(Enumerable.Range(0, MyThread) .Select(n => Task.Run(() => { for (int np = n * windowSize; np < (n + 1) * windowSize; np++) { double min = ColorDistance(MyList[np], palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistance(MyList[np], palette[i]); if (min > distance) { min = distance; pIndex = i; } } var p = np * 4; zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; } }))).Wait(); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //5:Taskを使った並列処理 private BitmapSource ZmT2減色5(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; int MyThread = Environment.ProcessorCount; int windowSize = pixels.Length / MyThread;//1スレッドに割り当てる量 Task.WhenAll(Enumerable.Range(0, MyThread) .Select(n => Task.Run(() => { for (int p = n * windowSize; p < (n + 1) * windowSize; p += 4) { var r = pixels[p + 2]; var g = pixels[p + 1]; var b = pixels[p]; double min = ColorDistance(r, g, b, palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistance(r, g, b, palette[i]); if (min > distance) { min = distance; pIndex = i; } } zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; } }))).Wait(); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //6:シングルスレッド+SIMD private BitmapSource Zs1SIMD減色6(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; for (int i = 0; i < OriginPixels.Length; i += 4) { double min = ColorDistanceV3(pixels[i + 2], pixels[i + 1], pixels[i], palette[0]); int pIndex = 0; //パレットから一番近い色 for (int pc = 1; pc < palette.Count; pc++) { //色の距離取得でSIMDのVectorを使用 var distance = ColorDistanceV3(pixels[i + 2], pixels[i + 1], pixels[i], palette[pc]); if (min > distance) { min = distance; pIndex = pc; } } //一番近い色に置き換える zPixels[i + 3] = 255;//A zPixels[i + 2] = palette[pIndex][0];//R zPixels[i + 1] = palette[pIndex][1];//G zPixels[i] = palette[pIndex][2];//B } return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //7:Parallelでの並列処理+SIMD private BitmapSource ZmPSIMD減色7(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; List<byte[]> MyList = MakeByteRGBA(pixels);//1ピクセルごとのRGBAのByte配列のリスト Parallel.For(0, MyList.Count, n => { double min = ColorDistanceV3(MyList[n], palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistanceV3(MyList[n], palette[i]); if (min > distance) { min = distance; pIndex = i; } } var p = n * 4; zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; }); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //8:Taskを使った並列処理+SIMD、1ピクセルごとのRGBAのByte配列のリスト private BitmapSource ZmT1SIMD減色8(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; List<byte[]> MyList = MakeByteRGBA(pixels);//1ピクセルごとのRGBAのByte配列のリスト int MyThread = Environment.ProcessorCount; int windowSize = MyList.Count / MyThread;//1スレッドに割り当てる量 Task.WhenAll(Enumerable.Range(0, MyThread) .Select(n => Task.Run(() => { for (int np = n * windowSize; np < (n + 1) * windowSize; np++) { double min = ColorDistanceV3(MyList[np], palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistanceV3(MyList[np], palette[i]); if (min > distance) { min = distance; pIndex = i; } } var p = np * 4; zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; } }))).Wait(); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //9:Taskを使った並列処理+SIMD private BitmapSource ZmT2SIMD減色9(BitmapSource source, byte[] pixels, List<byte[]> palette) { byte[] zPixels = new byte[pixels.Length]; int MyThread = Environment.ProcessorCount; int windowSize = pixels.Length / MyThread;//1スレッドに割り当てる量 Task.WhenAll(Enumerable.Range(0, MyThread) .Select(n => Task.Run(() => { for (int p = n * windowSize; p < (n + 1) * windowSize; p += 4) { var r = pixels[p + 2]; var g = pixels[p + 1]; var b = pixels[p]; double min = ColorDistanceV3(r, g, b, palette[0]); int pIndex = 0; for (int i = 1; i < palette.Count; i++) { var distance = ColorDistanceV3(r, g, b, palette[i]); if (min > distance) { min = distance; pIndex = i; } } zPixels[p] = palette[pIndex][2]; zPixels[p + 1] = palette[pIndex][1]; zPixels[p + 2] = palette[pIndex][0]; zPixels[p + 3] = 255; } }))).Wait(); return BitmapSource.Create(source.PixelWidth, source.PixelHeight, 96, 96, source.Format, null, zPixels, source.PixelWidth * 4); } //BGRAの配列をRGBAの4つごとのByteにする、ParallelForでの処理で使う private List<byte[]> MakeByteRGBA(byte[] pixels) { List<byte[]> MyList = new List<byte[]>(); for (int i = 0; i < pixels.Length; i += 4) { MyList.Add(new byte[] { pixels[i + 2], pixels[i + 1], pixels[i], pixels[i + 3] }); } return MyList; } //色の距離、Mathクラスで private double ColorDistance(byte r1, byte g1, byte b1, byte[] palette) { return Math.Sqrt( Math.Pow(r1 - palette[0], 2) + Math.Pow(g1 - palette[1], 2) + Math.Pow(b1 - palette[2], 2)); } private double ColorDistance(byte[] RGBA, byte[] palette) { return Math.Sqrt( Math.Pow(RGBA[0] - palette[0], 2) + Math.Pow(RGBA[1] - palette[1], 2) + Math.Pow(RGBA[2] - palette[2], 2)); } //色の距離、SIMDを使うVectorクラスで計算 private float ColorDistanceV3(byte r1, byte g1, byte b1, byte[] palette) { Vector3 value1 = new Vector3(r1, g1, b1); Vector3 value2 = new Vector3(palette[0], palette[1], palette[2]); return Vector3.Distance(value1, value2); } private float ColorDistanceV3(byte[] RGBA, byte[] palette) { Vector3 value1 = new Vector3(RGBA[0], RGBA[1], RGBA[2]); Vector3 value2 = new Vector3(palette[0], palette[1], palette[2]); return Vector3.Distance(value1, value2); } /// <summary> /// 画像ファイルからbitmapと、そのbyte配列を返す、ピクセルフォーマットは指定したものに変換 /// </summary> /// <param name="filePath">画像ファイルのフルパス</param> /// <param name="pixelFormat">PixelFormatsを指定、null指定ならBgra32で作成する</param> /// <param name="dpiX">96が基本、指定なしなら元画像と同じにする</param> /// <param name="dpiY">96が基本、指定なしなら元画像と同じにする</param> /// <returns></returns> private (byte[] pixels, BitmapSource source) MakeBitmapSourceAndPixelData( string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0) { byte[] pixels = null;//PixelData BitmapSource source = null; if (pixelFormat == null) { pixelFormat = PixelFormats.Bgra32; } try { using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) { var frame = BitmapFrame.Create(fs); var tempBitmap = new FormatConvertedBitmap(frame, pixelFormat, null, 0); int w = tempBitmap.PixelWidth; int h = tempBitmap.PixelHeight; int stride = (w * pixelFormat.BitsPerPixel + 7) / 8; pixels = new byte[h * stride]; tempBitmap.CopyPixels(pixels, stride, 0); //dpi指定がなければ元の画像と同じdpiにする if (dpiX == 0) { dpiX = frame.DpiX; } if (dpiY == 0) { dpiY = frame.DpiY; } //dpiを指定してBitmapSource作成 source = BitmapSource.Create( w, h, dpiX, dpiY, tempBitmap.Format, tempBitmap.Palette, pixels, stride); }; } catch (Exception) { } return (pixels, source); } private void MyGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Panel.SetZIndex(MyImageOrigin, 1); } private void MyGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Panel.SetZIndex(MyImageOrigin, -1); } private void ButtonSaveImage_Click(object sender, RoutedEventArgs e) { SaveImage((BitmapSource)MyImage.Source); } //処理時間計測 private void MyTime(Func<BitmapSource, byte[], List<byte[]>, BitmapSource> func, TextBlock textBlock) { if (OriginBitmapSource == null) return; var sw = new Stopwatch(); sw.Start(); //MyImage.Source = func(OriginBitmapSource, OriginPixels, MakePalette()); MyImage.Source = func(OriginBitmapSource, OriginPixels, MakePalette(16)); sw.Stop(); textBlock.Text = $"{sw.Elapsed.Seconds}秒{sw.Elapsed.Milliseconds.ToString("000")}"; } private void ButtonExe1_Click(object sender, RoutedEventArgs e) { MyTime(Zs1減色1, tbTime1); } private void ButtonExe2_Click(object sender, RoutedEventArgs e) { MyTime(Zs2減色2, tbTime2); } private void ButtonExe3_Click(object sender, RoutedEventArgs e) { MyTime(ZmP減色3, tbTime3); } private void ButtonExe4_Click(object sender, RoutedEventArgs e) { MyTime(ZmT1減色4, tbTime4); } private void ButtonExe5_Click(object sender, RoutedEventArgs e) { MyTime(ZmT2減色5, tbTime5); } private void ButtonExe6_Click(object sender, RoutedEventArgs e) { MyTime(Zs1SIMD減色6, tbTime6); } private void ButtonExe7_Click(object sender, RoutedEventArgs e) { MyTime(ZmPSIMD減色7, tbTime7); } private void ButtonExe8_Click(object sender, RoutedEventArgs e) { MyTime(ZmT1SIMD減色8, tbTime8); } private void ButtonExe9_Click(object sender, RoutedEventArgs e) { MyTime(ZmT2SIMD減色9, tbTime9); } private void SaveImage(BitmapSource source) { var saveFileDialog = new Microsoft.Win32.SaveFileDialog(); saveFileDialog.Filter = "*.png|*.png|*.bmp|*.bmp|*.tiff|*.tiff"; saveFileDialog.AddExtension = true; saveFileDialog.FileName = System.IO.Path.GetFileNameWithoutExtension(ImageFileFullPath) + "_"; saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(ImageFileFullPath); if (saveFileDialog.ShowDialog() == true) { BitmapEncoder encoder = new BmpBitmapEncoder(); if (saveFileDialog.FilterIndex == 1) { encoder = new PngBitmapEncoder(); } else if (saveFileDialog.FilterIndex == 2) { encoder = new BmpBitmapEncoder(); } else if (saveFileDialog.FilterIndex == 3) { encoder = new TiffBitmapEncoder(); } encoder.Frames.Add(BitmapFrame.Create(source)); using (var fs = new System.IO.FileStream(saveFileDialog.FileName, System.IO.FileMode.Create, System.IO.FileAccess.Write)) { encoder.Save(fs); } } } } }
16秒かかるこれを
マルチスレッド化してSIMDを使うように、こう書くと0.4秒ってのは魔法みたい、っていうのもTaskを使ったマルチスレッド処理の部分は、コピペ改変なのでよくわかっていない。処理する配列の要素をスレッド数で割って(344行目)、各スレッドに割り当てる範囲を決めている(349行目のfor)みたいなのはなんとなくわかるとして、Task.WhenAllとTask.Runのあたりが、ラムダ式とLINQも相まってわからなさを加速させている。いつかはこういうコードもサラッと書けるようになりたいねえ
2020/01/22追記ここから
Taskを使った並列処理のところは、ピクセル数がCPUスレッド数で割り切れなかった場合は、処理されないピクセルが出る?
ピクセル数10でスレッド数が8の場合、10/8=1…余り2で、最後の2ピクセルは処理されないはずだけど、言わなければバレへんか…
2020/01/22追記ここまで
いいねえ
関連記事
System.Numerics.Vectorクラス使ってみたその2は1週間後
3日前
2年前
中途半端なできのまま放置されているこの減色アプリを作り直したい