大きめの画像3072×2304 Color画像を、Core i7 860 の4Core 8Thread を使用して、3×3の8近傍ラプラシアンフィルターを実行した場合、シリアル処理で約4.66秒を必要となった。これをパラレル実行することで、約4倍の1.11秒まで高速化することができた。
この時のコードは、カラーのループ、フィルターのループを素直?にfor ループで実装している。また、フィルターもfloatによる積和演算を行っているため、まだコード上の高速化の余地は残っている。では、さらにコード上での高速化を図った場合にどこまで高速化できるのか、興味がわいてきたので、実際に測定してみた。
結果は次の表のとおり。
番号 | 説明 | シリアル実行(秒) | パラレル実行(秒) |
1 | 素直にフィルターを実装 | 4.66 | 1.11 |
2 | フィルターの2重for ループを展開 | 3.04 | 0.81 |
3 | float による積和をint 加減算とシフトに変更 | 2.11 | 0.51 |
4 | Color のループを展開 | 1.60 | 0.40 |
シリアル実行の場合は、コード上でカラー、フィルターの3重ループを展開し、float の積和をint の加減算に変更することにより、4.66秒が1.60秒と、約3倍の高速化が可能という結果となった。
さらに、これを行単位でのパラレル実行した場合、なにも考慮しないシリアル実行の場合の4.66秒に対して、最終的に0.40秒と、約10倍の高速化となった。
注目すべき点は、ループの展開や積和を加減算に変更するなどといった、従来型のソフトウェアチューニングが約3倍の高速化だったのに対して、単純にパラレル展開して、4Core 8Thread をフルに使うことにより、非常に簡単に約4倍の高速化が得られている点である。
すでに4Core,6Coreなどのメニーコアが手軽に入手できるようになっており、コードをチューニングするよりは、わかりやすいコードを維持したまま、パラレル展開するほうが望ましいという時代が到来したということだろう。
ただし、今回の画像処理のようなパラレル処理に向いた処理は限られているので、どのような場合に並列処理を適用すべきか、慎重に判断すべきである。また、処理が複雑になれば思わぬデッドロックやデータ競合が発生することもあり、中途半端な知識で並列処理を適用するのはやめたほうがいい。
一応、フィルターのコード(No.1とNo.4)をさらしておく。
#1のコード
private void FilterOneLine(int y)
{
int str = Width * BytesPerPixel;
for (int x = 1; x < Width – 1; x += 1)
{
// 各色ごとに
for (int col = 0; col < BytesPerPixel; col++)
{
int index = str * y + BytesPerPixel * x + col;
if (col == 3)
{
Result[index] = 255;
continue;
}
float v = 0f;
for (int yy = -1; yy <= 1; yy++)
for (int xx = -1; xx <= 1; xx++)
{
int position = str * (y + yy) + BytesPerPixel * (x + xx) + col;
float filterValue = filter[yy + 1, xx + 1];
v = v + Source[position] * filterValue;
}
v = Math.Abs(v);
v = (v > 10) ? 255 : 0;
Result[index] = (byte)v;
}
}
}
#4のコード
private void FilterOneLine3(int y)
{
int str = Width * BytesPerPixel;
for (int x = 1; x < Width – 1; x += 1)
{
int index = 0;
int v;
index = str * y + BytesPerPixel * x;
// col == 0
v =
Source[index – str – BytesPerPixel] +
Source[index – str] +
Source[index – str + BytesPerPixel] +
Source[index – BytesPerPixel] +
-8 * Source[index] +
Source[index + BytesPerPixel] +
Source[index + str – BytesPerPixel] +
Source[index + str] +
Source[index + str + BytesPerPixel];
v = Math.Abs(v / 8);
v = (v > 10) ? 255 : 0;
Result[index] = (byte)v;
// col == 1
index++;
v =
Source[index – str – BytesPerPixel] +
Source[index – str] +
Source[index – str + BytesPerPixel] +
Source[index – BytesPerPixel] +
-8 * Source[index] +
Source[index + BytesPerPixel] +
Source[index + str – BytesPerPixel] +
Source[index + str] +
Source[index + str + BytesPerPixel];
v = Math.Abs(v / 8);
v = (v > 10) ? 255 : 0;
Result[index] = (byte)v;
// col == 2
index++;
v =
Source[index – str – BytesPerPixel] +
Source[index – str] +
Source[index – str + BytesPerPixel] +
Source[index – BytesPerPixel] +
-8 * Source[index] +
Source[index + BytesPerPixel] +
Source[index + str – BytesPerPixel] +
Source[index + str] +
Source[index + str + BytesPerPixel];
v = Math.Abs(v / 8);
v = (v > 10) ? 255 : 0;
Result[index] = (byte)v;
// col == 3
index++;
Result[index] = 255;
}
}
コメントを残す