午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

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(List colors)
{
	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倍の処理時間
 
 
イメージ 1
これだとLINQのAsParallelが結構効いている
AsParallelでの速度上昇は1~2割くらいで結構ぶれ幅がある
 
処理中のCPU使用率

f:id:gogowaten:20191213140615p:plain

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();
と思ったんだけど、これだとエラーになって

f:id:gogowaten:20191213140633p:plain

double ave = AryRed.Average(a => a);
こう書いたらうまく動いた
 
 
 
関連記事
続きの次回、2018/12/01
WPF、Parallel.Invoke、並列実行は昨日より速く ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15775527.html
イメージ 4