カーネルのパフォーマンスに影響を与える根本的な要因を理解できないようです。2 つの単純なカーネルを実装しました。1 つは 2 つの画像を読み込んでピクセルごとに加算するカーネル、もう 1 つは 2 つの画像を読み込んでビット単位のAND 演算を行うカーネルです。ここで、カーネルが 1、3、および 4 チャネルのイメージだけでなく、8 ビットおよび 32 ビットのイメージを取得できるように、それらをテンプレート化しました。
そのため、最初は両方のカーネルでグローバル メモリをuchar3
およびなどfloat3
と一緒にロードしていましたuchar4
。しかし、合体のためにトリプルを使用することについてあまり確信が持てなかったので、プロファイリングを実行することにしました。操作はチャネル番号に依存しないため、実際uchar
の画像ではなく、幅が 3 倍の 1 チャネルの画像であるかのように画像を読み取ることができると考えましuchar3
た。
実際、uchar3
グローバル ロードは、ロードよりもはるかに遅くなりましたuchar
。私の努力は報われました。しかし、残念ながら、これは算術カーネルでのみ発生しました。ビットごとの AND 演算は、正反対の結果を示しました。
uint
これで、画像データをs ではなくuchar
s としてロードするだけで、ビットごとの操作が可能になり、合体を完全に処理できることがわかりました。しかし、何が起こっているのかを学び、理解したいだけだと仮定しましょう。
float3
s やs などのことは忘れましょうfloat4
。私の問題はuchar
、カーネルのバージョンにあります。では、簡単に言えば、なぜ負荷はuchar
負荷よりも速い場合uchar3
とそうでない場合があるのでしょうか?
GTX 470、コンピューティング機能 2.0 を使用しています。
PS。CUDA プログラミング ガイドによると、論理演算と加算演算のスループットは同じです。uchar
(私のカーネルは実際には最初にs をs に変換するuint
必要がありますが、それは両方のカーネルで行われているはずです。) したがって、実行の長さは、私が収集したものとほぼ同じになるはずです。
算術加算カーネル (uchar
バージョン):
__global__ void add_8uc1(uchar* inputOne, uchar* inputTwo, uchar* output, unsigned int width, unsigned int height, unsigned int widthStep)
{
const int xCoordinateBase = blockIdx.x * IMAGE_X * IMAGE_MULTIPLIER + threadIdx.x;
const int yCoordinate = blockIdx.y * IMAGE_Y + threadIdx.y;
if (yCoordinate >= height)
return;
#pragma unroll IMAGE_MULTIPLIER
for (int i = 0; i < IMAGE_MULTIPLIER && xCoordinateBase + i * IMAGE_X < width; ++i)
{
// Load memory.
uchar* inputElementOne = (inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
uchar* inputElementTwo = (inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
// Write output.
*(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x)) = inputElementOne[0] + inputElementTwo[0];
}
}
ビットごとの AND カーネル:
__global__ void and_8uc1(uchar* inputOne, uchar* inputTwo, uchar* output, unsigned int width, unsigned int height, unsigned int widthStep)
{
const int xCoordinateBase = blockIdx.x * IMAGE_X * IMAGE_MULTIPLIER + threadIdx.x;
const int yCoordinate = blockIdx.y * IMAGE_Y + threadIdx.y;
if (yCoordinate >= height)
return;
#pragma unroll IMAGE_MULTIPLIER
for (int i = 0; i < IMAGE_MULTIPLIER && xCoordinateBase + i * IMAGE_X < width; ++i)
{
// Load memory.
uchar* inputElementOne = (inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
uchar* inputElementTwo = (inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x));
// Write output.
*(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x)) = inputElementOne[0] & inputElementTwo[0];
}
}
uchar3
ロード/ストア行が次のようになったことを除いて、バージョンは同じです。
// Load memory.
uchar3 inputElementOne = *reinterpret_cast<uchar3*>(inputOne + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3);
uchar3 inputElementTwo = *reinterpret_cast<uchar3*>(inputTwo + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3);
// Write output.
*reinterpret_cast<uchar3*>(output + yCoordinate * widthStep + (xCoordinateBase + i * IMAGE_X + threadIdx.x) * 3)
= make_uchar3(inputElementOne.x + inputElementTwo.x, inputElementOne.y + inputElementTwo.y, inputElementOne.z + inputElementTwo.z);
AND カーネルについても同様です。(正直なところ、カーネルを正確に覚えているかどうかはわかりません...明日確認します)。