LINQのMin、Max、Average使ってみたけど遅い、画像処理には向かない
LINQはとても便利だけど場合によっては遅い
今回試したのは画像処理で使うbyte型配列
ここからRGBそれぞれの配列と最低値、最大値、平均値を求めたい
これらは減色処理で使う
ピクセルフォーマットがRGBの場合はRGBの順番で並んでいる
[R,G,B,R,G,B…]
変数名 型 内容 source byte[] 元になるRGBの配列、byte型 AryRed byte[] Rの配列の要素数はsourceの要素数の1/3 AryGre byte[] G AryBlu byte[] B MinRed byte Rの最低値 MaxRed byte Rの最大値 AveRed double Rの平均値
LINQなしで普通に書いたのがTestA
private void TestA_for_if(byte[] source) { int len = source.Length / 3; MinRed = 255; MaxRed = 0; MinGre = 255; MaxGre = 0; MinBlu = 255; MaxBlu = 0; byte r, g, b; long tR = 0, tG = 0, tB = 0;//合計値 for (int i = 0; i < len; i++) { r = source[i * 3]; g = source[i * 3 + 1]; b = source[i * 3 + 2]; AryRed[i] = r; AryGre[i] = g; AryBlu[i] = b; tR += r; tG += g; tB += b; if (MinRed > r) { MinRed = r; } if (MaxRed < r) { MaxRed = r; } if (MinGre > g) { MinGre = g; } if (MaxGre < g) { MaxGre = g; } if (MinBlu > b) { MinBlu = b; } if (MaxBlu < b) { MaxBlu = b; } } AveRed = tR / (double)len; AveGre = tG / (double)len; AveBlu = tB / (double)len; }
結構めんどくさい
LINQを使って書いたTestB
private void TestB_LINQ(byte[] source) { int len = source.Length / 3; for (int i = 0; i < len; i++) { AryRed[i] = source[i * 3]; AryGre[i] = source[i * 3 + 1]; AryBlu[i] = source[i * 3 + 2]; } MinRed = AryRed.Min(); MaxRed = AryRed.Max(); AveRed = AryRed.Average(a => a); MinGre = AryGre.Min(); MaxGre = AryGre.Max(); AveGre = AryGre.Average(a => a); MinBlu = AryBlu.Min(); MaxBlu = AryBlu.Max(); AveBlu = AryBlu.Average(a => a); }
RGBそれぞれの配列を作って、配列にLINQのMin、Max、Averageメソッドで簡単に得られる!けど、遅い!
ピクセル数100万を20回処理した場合
0.58秒 普通 2.56秒 LINQ 普通より5倍近く時間かかる PC環境 OS Windows 10 Home 64bit CPU AMD Phenom(tm) II X3 720 Processor 基本速度: 3.00 GHz ソケット: 1 コア: 3 論理プロセッサ数: 3 仮想化: 有効 L1 キャッシュ: 384 KB L2 キャッシュ: 1.5 MB L3 キャッシュ: 6.0 MB
LINQの並列処理
AsParallelってのをつけると並列処理してくれるので処理が速くなる
MinRed = AryRed.Min();
これを
MinRed = AryRed.AsParallel().Min();
こうするだけで並列処理してくれる!
ほかのLINQのところにもAsParallelを付け足した結果、処理時間は
2.29秒、1割程度速くなった(2.29/2.56=0.89)
うーん、あんまり変わらない、CPUのコア数が多ければもっと速くなるのかも
以前の方法
以前はbyte型ではなくColor型の配列を使っていた
これは色なんだからbyte型よりColor型のほうが、わかりやすいって理由だったかな
LINQのこともよく知らなかったのでこうだった
TestAをColor型にしただけのTestOld
private void TestOld(Listcolors) { int count = colors.Count; MinRed = 255; MaxRed = 0; MinGre = 255; MaxGre = 0; MinBlu = 255; MaxBlu = 0; byte r, g, b; Color col; long rTotal = 0, gTotal = 0, bTotal = 0; for (int i = 0; i < count; i++) { col = colors[i]; r = col.R; g = col.G; b = col.B; AryRed[i] = r; AryGre[i] = g; AryBlu[i] = b; rTotal += r; gTotal += g; bTotal += b; if (MinRed > r) { MinRed = r; } if (MaxRed < r) { MaxRed = r; } if (MinGre > g) { MinGre = g; } if (MaxGre < g) { MaxGre = g; } if (MinBlu > b) { MinBlu = b; } if (MaxBlu < b) { MaxBlu = b; } } AveRed = rTotal / (double)count; AveGre = gTotal / (double)count; AveBlu = bTotal / (double)count; }
これだと1.12秒
byte型より2倍の処理時間
これだとLINQのAsParallelが結構効いている
AsParallelでの速度上昇は1~2割くらいで結構ぶれ幅がある
処理中のCPU使用率
TestA、Old、BはCPUの3コアのうち1コアだけしか使っていないから
50%前後の使用率
並列処理のAsParallel付きは100%近い使用率
なのでAsParallelは効いているけど、使用率の割には速くなってないねえ
それでも簡単に速くできるんだからかなり便利
わかったこと
画像処理にはColor型よりbyte型を使ったほうが速い
画像処理でLINQのMin、Max、Averageを単純に使うような処理だとかなり遅くなるので普通に書いたほうがいい
LINQをAsParallelで並列化しても1~2割しか速くならなかった
よくわからん
LINQのAverageの使い方は
double ave = AryRed.Average();
と思ったんだけど、これだとエラーになって
double ave = AryRed.Average(a => a);
こう書いたらうまく動いた
関連記事
続きの次回、2018/12/01
WPF、Parallel.Invoke、並列実行は昨日より速く ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15775527.html