消えたCUDA関連の旧ブログ記事を復元するひとり Advent Calendar 2024の記事です。

pragma unrollと本記事の内容について

CUDAの最適化の一つとしてfor文のunrollがあると思うのですが,単に

#pragma unroll
for (unsigned i = 0; i < 32; i++) {
    // iroiro
}
のように書くだけではなく,いろいろ指示を出せますよという話です.

色々できるよunrolling

そもそもCUDAではnvccがunrollしたほうが良さそうと判断した場合には自動でunrollされます.
これはこれで困る場合もあるかもしれません(本当に?).
こんなときは

#pragma unroll 1
for (unsigned i = 0; i < 32; i++) {
    // iroiro
}
のようにunrollの後に1 を書いておくとunrollされなくなります.
この1は何かと言うと,unrollの展開数です.
つまり
#pragma unroll 4
for (unsigned i = 0; i < 32; i++) {
    // iroiro
}
と書くと4回のみunrollされたものをfor文で8回回します,これを4ではなく1と書くことでunrollを無効化できるわけです.
この様にunrollは展開数を指定することができます.
forの回数が段数で割り切れなかった場合はPTXになる段階でうまい具合に調整されます(余りの文を最初処理し,残りをループ処理します).

このunrollの展開数ですが,こんな感じでコンパイル時に値が判明している定数でも指定することができます.

void func ( ... ) {
    // iroiro
#pragma unroll N
    for (unsigned i = 0; i < 32; i++) {
        // iroiro
    }
    // iroiro
}
なんなら計算もできます.
void func ( ... ) {
    // iroiro
#pragma unroll (N + 4)
    for (unsigned i = 0; i < 32; i++) {
        // iroiro
    }
    // iroiro
}

おまけ

pragma unrollはコンパイル時にループ回数が分かっていないループにも少し使えます.
そもそもループ回数がコンパイル時に分かっていない場合も少し展開され,デフォルトでの展開のされ方は上の展開方法の展開数4の場合と似ています.
はじめにループ回数を4で割ったあまりを求め,この回数だけfor内の処理を行います.
あとは展開数4のループを回す感じです.
そこでこのコンパイル時にループ回数が分かっていないfor文に対してpragma unrollをすると,同じ様に展開数を指定できたりします(こちらでは端数処理は最後に行われるようです).

参考

CUDA Toolkit Document - Programming guide - B.24. #pragma unroll