0

私はCUDAが初めてです。手を汚すために、エラトステネスのふるいを書いてみました (ある数 n までのすべての素数を見つけるため)。

それを機能させるために私がしなければならなかったことがたくさんありますが、それは必要ではなかったようです。より自然な (そして CUDA に最適化された) アプローチを誰かが知っているかどうか、私は興味があります。

  1. isPrime 配列でプライムとしてマークされたエントリを取得するには、2 つの別個のカーネル呼び出しを実行する必要がありました。1 つ目は、各スレッドブロック内の素数の数をカウントし、各エントリ i に、そのブロック内の i 未満の素数の数を割り当てます。次に、最終的なインデックスを取得するために、前のすべてのブロックの素数を追加する 2 番目の呼び出しを行う必要があります。
  2. しかし、それよりもさらに悪いのは、同時読み取りのヒープを回避するために、ブロック内の素数の数を THREADS_PER_BLOCK インデックスごとに個別の配列に格納する必要があったため、アルゴリズムに必要なメモリが事実上 2 倍になったためです。何度もコピーするのではなく、すべてのスレッドが各ブロックに対して同じ値を読み取るようにする方法が必要なようです。
  3. これらすべてにもかかわらず、clearMultiples メソッドにはまだ同時読み取りの問題があります。特に 2 や 3 のような小さな素数の場合、すべてのスレッドで値を読み込む必要があります。これを処理する方法はありませんか?

誰かが私のコードを見て、私ができることが明らかで、より単純またはより効率的であるかどうか教えてもらえますか?

特に非効率的なことはありますか(コースの最後にすべての素数を出力する以外に)?

カーネル呼び出しのたびに synchronize を呼び出す必要がありますか?

memcpy の後にも同期する必要がありますか?

最後に、THREADS_PER_BLOCK を 512 に設定しても機能しないのはなぜですか?

ありがとうございました

#include <stdio.h>
#include <cuda.h>
#include <assert.h>
#include <math.h>

#define MAX_BLOCKS 256
#define THREADS_PER_BLOCK 256 //Must be a power of 2
#define BLOCK_SPACE 2 * THREADS_PER_BLOCK

__global__ void initialize(int* isPrime, int n) {
    int idx = blockIdx.x * THREADS_PER_BLOCK + threadIdx.x;
    int step = gridDim.x * THREADS_PER_BLOCK;
    int i;
    for (i = idx; i <= 1; i += step) {
        isPrime[i] = 0;
    }
    for (; i < n; i += step) {
        isPrime[i] = 1;
    }
}

__global__ void clearMultiples(int* isPrime, int* primeList, int startInd,
        int endInd, int n) {
    int yidx = blockIdx.y * blockDim.y + threadIdx.y;
    int xidx = blockIdx.x * blockDim.x + threadIdx.x;
    int ystep = gridDim.y * blockDim.y;
    int xstep = gridDim.x * blockDim.x;
    for (int pnum = startInd + yidx; pnum < endInd; pnum += ystep) {
        int p = primeList[pnum];
        int pstart = p * (p + xidx);
        int pstep = p * xstep;
        for (int i = pstart; i < n; i += pstep) {
            isPrime[i] = 0;
        }
    }
}

__device__ void makeCounts(int* isPrime, int* addend, int start, int stop) {
    __shared__ int tmpCounts[BLOCK_SPACE];
    __shared__ int dumbCounts[BLOCK_SPACE];
    int idx = threadIdx.x;
    tmpCounts[idx] = ((start + idx) < stop) ? isPrime[start + idx] : 0;
    __syncthreads();
    int numEntries = THREADS_PER_BLOCK;
    int cstart = 0;
    while (numEntries > 1) {
        int prevStart = cstart;
        cstart += numEntries;
        numEntries /= 2;
        if (idx < numEntries) {
            int i1 = idx * 2 + prevStart;
            tmpCounts[idx + cstart] = tmpCounts[i1] + tmpCounts[i1 + 1];
        }
        __syncthreads();
    }
    if (idx == 0) {
        dumbCounts[cstart] = tmpCounts[cstart];
        tmpCounts[cstart] = 0;
    }
    while (cstart > 0) {
        int prevStart = cstart;
        cstart -= numEntries * 2;
        if (idx < numEntries) {
            int v1 = tmpCounts[idx + prevStart];
            int i1 = idx * 2 + cstart;
            tmpCounts[i1 + 1] = tmpCounts[i1] + v1;
            tmpCounts[i1] = v1;
            dumbCounts[i1] = dumbCounts[i1 + 1] = dumbCounts[idx + prevStart];
        }
        numEntries *= 2;
        __syncthreads();
    }
    if (start + idx < stop) {
        isPrime[start + idx] = tmpCounts[idx];
        addend[start + idx] = dumbCounts[idx];
    }
}

__global__ void createCounts(int* isPrime, int* addend, int lb, int ub) {
    int step = gridDim.x * THREADS_PER_BLOCK;
    for (int i = lb + blockIdx.x * THREADS_PER_BLOCK; i < ub; i += step) {
        int start = i;
        int stop = min(i + step, ub);
        makeCounts(isPrime, addend, start, stop);
    }
}

__global__ void sumCounts(int* isPrime, int* addend, int lb, int ub,
        int* totalsum) {
    int idx = blockIdx.x;
    int s = 0;
    for (int i = lb + idx; i < ub; i += THREADS_PER_BLOCK) {
        isPrime[i] += s;
        s += addend[i];
    }
    if (idx == 0) {
        *totalsum = s;
    }
}

__global__ void condensePrimes(int* isPrime, int* primeList, int lb, int ub,
        int primeStartInd, int primeCount) {
    int idx = blockIdx.x * THREADS_PER_BLOCK + threadIdx.x;
    int step = gridDim.x * THREADS_PER_BLOCK;
    for (int i = lb + idx; i < ub; i += step) {
        int term = isPrime[i];
        int nextTerm = i + 1 == ub ? primeCount : isPrime[i + 1];
        if (term < nextTerm) {
            primeList[primeStartInd + term] = i;
        }
    }
}

int main(void) {
    printf("Enter upper bound:\n");
    int n;
    scanf("%d", &n);

    int *isPrime, *addend, *numPrimes, *primeList;
    cudaError_t t = cudaMalloc((void**) &isPrime, n * sizeof(int));
    assert(t == cudaSuccess);
    t = cudaMalloc(&addend, n * sizeof(int));
    assert(t == cudaSuccess);
    t = cudaMalloc(&numPrimes, sizeof(int));
    assert(t == cudaSuccess);
    int primeBound = 2 * n / log(n);
    t = cudaMalloc(&primeList, primeBound * sizeof(int));
    assert(t == cudaSuccess);
    int numBlocks = min(MAX_BLOCKS,
            (n + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK);
    initialize<<<numBlocks, THREADS_PER_BLOCK>>>(isPrime, n);
    t = cudaDeviceSynchronize();
    assert(t == cudaSuccess);

    int bound = (int) ceil(sqrt(n));

    int lb;
    int ub = 2;
    int primeStartInd = 0;
    int primeEndInd = 0;

    while (ub < n) {
        if (primeEndInd > primeStartInd) {
            int lowprime;
            t = cudaMemcpy(&lowprime, primeList + primeStartInd, sizeof(int),
                    cudaMemcpyDeviceToHost);
            assert(t == cudaSuccess);
            int numcols = n / lowprime;
            int numrows = primeEndInd - primeStartInd;
            int threadx = min(numcols, THREADS_PER_BLOCK);
            int thready = min(numrows, THREADS_PER_BLOCK / threadx);
            int blockx = min(numcols / threadx, MAX_BLOCKS);
            int blocky = min(numrows / thready, MAX_BLOCKS / blockx);
            dim3 gridsize(blockx, blocky);
            dim3 blocksize(threadx, thready);
            clearMultiples<<<gridsize, blocksize>>>(isPrime, primeList,
                    primeStartInd, primeEndInd, n);
            t = cudaDeviceSynchronize();
            assert(t == cudaSuccess);
        }
        lb = ub;
        ub *= 2;
        if (lb >= bound) {
            ub = n;
        }
        numBlocks = min(MAX_BLOCKS,
                (ub - lb + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK);

        createCounts<<<numBlocks, THREADS_PER_BLOCK>>>(isPrime, addend, lb, ub);
        t = cudaDeviceSynchronize();
        assert(t == cudaSuccess);

        sumCounts<<<THREADS_PER_BLOCK, 1>>>(isPrime, addend, lb, ub, numPrimes);
        t = cudaDeviceSynchronize();
        assert(t == cudaSuccess);

        int primeCount;
        t = cudaMemcpy(&primeCount, numPrimes, sizeof(int),
                cudaMemcpyDeviceToHost);
        assert(t == cudaSuccess);
        assert(primeCount > 0);

        primeStartInd = primeEndInd;
        primeEndInd += primeCount;
        condensePrimes<<<numBlocks, THREADS_PER_BLOCK>>>(isPrime, primeList, lb,
                ub, primeStartInd, primeCount);
        t = cudaDeviceSynchronize();
        assert(t == cudaSuccess);
    }
    int finalprimes[primeEndInd];
    t = cudaMemcpy(finalprimes, primeList, primeEndInd * sizeof(int),
            cudaMemcpyDeviceToHost);
    assert(t == cudaSuccess);

    t = cudaFree(isPrime);
    assert(t == cudaSuccess);
    t = cudaFree(addend);
    assert(t == cudaSuccess);
    t = cudaFree(numPrimes);
    assert(t == cudaSuccess);
    t = cudaFree(primeList);
    assert(t == cudaSuccess);
    for (int i = 0; i < primeEndInd; i++) {
        if (i % 16 == 0)
            printf("\n");
        else
            printf(" ");
        printf("%4d", finalprimes[i]);
    }
    printf("\n");
    return 0;
}
4

1 に答える 1

1

あなたの質問のいくつかに答えます。

  1. コメントで定義されているエラー チェックを修正します。

  2. 「同時読み取り」の意味を定義してください。あなたはこれについて心配していますが、それが何を意味するのかわかりません。

カーネル呼び出しのたびに synchronize を呼び出す必要がありますか?

いいえ、そうではありません。コードが正しく動作していない場合、すべてのカーネル呼び出しの後に同期し、適切なエラー チェックを行うと、カーネルが正しく起動していないかどうかがわかります。このような比較的単純な単一ストリーム プログラムでは、通常、同期は必要ありません。cudaMemcpy のように同期する必要がある cuda 呼び出しは、これを自動的に行います。

memcpy の後にも同期する必要がありますか?

いいえ、cudaMemcpy は本質的に同期的です (開始前に同じストリーム内のすべての cuda 呼び出しを強制的に完了させ、コピーが完了するまで制御をホスト スレッドに返しません)。 (完了するまでホスト スレッドに制御を返さない) その後、呼び出しの cudaMemcpyAsync バージョンを使用できます。以前のすべての cuda 呼び出しを強制的に完了するという動作を回避するには、ストリームを使用します。

最後に、THREADS_PER_BLOCK を 512 に設定しても機能しないのはなぜですか?

「うまくいかない」の定義を教えてください。コードを 512 と 256 の THREADS_PER_BLOCK でコンパイルしました。上限が 1000 の場合、それぞれの場合で同じ出力が得られました。

于 2013-03-25T23:38:49.000 に答える