Windows 7, NVidia GeForce 425M.
行列の行の合計を計算する単純な CUDA コードを作成しました。行列は 1 次元表現 (float へのポインター) です。
コードのシリアル バージョンは次のとおりです (2
予想どおり、ループがあります)。
void serial_rowSum (float* m, float* output, int nrow, int ncol) {
float sum;
for (int i = 0 ; i < nrow ; i++) {
sum = 0;
for (int j = 0 ; j < ncol ; j++)
sum += m[i*ncol+j];
output[i] = sum;
}
}
CUDA コード内で、行列を行単位でスイープするカーネル関数を呼び出します。以下は、カーネル呼び出しのスニペットです。
dim3 threadsPerBlock((unsigned int) nThreadsPerBlock); // has to be multiple of 32
dim3 blocksPerGrid((unsigned int) ceil(nrow/(float) nThreadsPerBlock));
kernel_rowSum<<<blocksPerGrid, threadsPerBlock>>>(d_m, d_output, nrow, ncol);
行の並列合計を実行するカーネル関数 (まだ1
ループがあります):
__global__ void kernel_rowSum(float *m, float *s, int nrow, int ncol) {
int rowIdx = threadIdx.x + blockIdx.x * blockDim.x;
if (rowIdx < nrow) {
float sum=0;
for (int k = 0 ; k < ncol ; k++)
sum+=m[rowIdx*ncol+k];
s[rowIdx] = sum;
}
}
ここまでは順調ですね。シリアルとパラレル (CUDA) の結果は同じです。
nThreadsPerBlock
要点は、パラメータを変更nThreadsPerBlock
し32
ても、CUDA バージョンの計算にはシリアル バージョンのほぼ 2 倍の時間がかかるということ1024
です。
IMO、行列の次元は並列化を正当化するのに十分な大きさです: 90,000 x 1,000
.
以下では、異なる を使用して、シリアル バージョンとパラレル バージョンの経過時間を報告しますnThreadsPerBlock
。サンプルmsec
の平均で報告された時間:100
マトリックス: nrow = 90000 x ncol = 1000
シリアル: ミリ秒単位のサンプルあたりの平均経過時間 (100
サンプル): 289.18
.
CUDA ( 32
ThreadsPerBlock): サンプルあたりの平均経過時間 (ミリ秒) ( 100
samples): 497.11
.
CUDA ( 1024
ThreadsPerBlock): サンプルあたりの平均経過時間 (ミリ秒) ( 100
samples): 699.66
.
念のため、 / が付いているバージョンが32
最速1024
nThreadsPerBlock
/最遅です。
ホストからデバイスにコピーするとき、およびその逆のときにある種のオーバーヘッドがあることは理解していますが、速度が遅いのは、最速のコードを実装していないためかもしれません。
私はCUDAの専門家にはほど遠いので:
このタスクの最速バージョンをコーディングしていますか? コードを改善するにはどうすればよいですか? カーネル関数のループを取り除くことはできますか?
どんな考えでも大歓迎です。
編集1
標準について説明しますが、 /のような値を持つ行の/操作にrowSum
興味があります。とはいえ、一部のコメンテーターが示唆しているように、の列ベクトル トリックによる乗算を悪用することはできません。AND
OR
(0;1}
rowAND
rowOR
cuBLAS
1
COL
編集2
ユーザーが提案したように、他のユーザーとここで承認されました:
独自の関数を記述しようとすることを忘れて、代わりに Thrust ライブラリを使用すると、魔法が起こります。