CUDA - Eratosthenes筛分为部分

时间:2015-06-21 07:38:29

标签: c cuda parallel-processing primes sieve-of-eratosthenes

我在GPU上编写了Eratosthenes的Sieve(https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes)的实现。但不是这样的 - http://developer-resource.blogspot.com/2008/07/cuda-sieve-of-eratosthenes.html

方法:

  1. 使用默认值0/1(0 - prime,1 - no)创建n元素数组并将其传递给GPU(我知道它可以直接在内核中完成,但在这一刻它不是问题)。
  2. 块中的每个线程检查单个数字的倍数。每个块检查总sqrt(n)的可能性。每个块==不同的间隔。
  3. 将倍数标记为1并将数据传回主机。
  4. 代码:

    <!DOCTYPE html>
    <html>
       <body>
          <p id="demo"></p>
          <input type="button" id="myBtn" onclick="myFunction()" value="1">
          <input type="button" id="myBtn" onclick="myFunction()" value="2">
          <input type="button" id="myBtn" onclick="myFunction()" value="3">
          <input type="button" id="myBtn" onclick="myFunction()" value="4">
          <input type="button" id="myBtn" onclick="myFunction()" value="5">
          <input type="button" id="myBtn" onclick="myFunction()" value="6">
          <input type="button" id="myBtn" onclick="myFunction()" value="7">
          <input type="button" id="myBtn" onclick="myFunction()" value="8">
          <input type="button" id="myBtn" onclick="myFunction()" value="9">
          <input type="button" id="myBtn" onclick="myFunction()" value="0">
          <script>
             function myFunction() {
                 var x = document.getElementById("myBtn").value;
                 document.getElementById("demo").innerHTML = x;
             }
          </script>
       </body>
    </html>


    跑: ./sieve 10240000

    当n = 16,64,1024,102400时它正常工作...但是对于n = 10240000我得到的结果不正确。哪里有问题?

2 个答案:

答案 0 :(得分:2)

我认为有几个问题,但这里是指向实际问题的指针:Eratosthenes的筛子去除了已经遇到的素数的迭代倍数,并且您想要将工作量分成线程 - 块,每个线程块在一块共享内存上运行(在您的示例中为缓存)。但是,线程块通常独立于所有其他线程块,并且不容易彼此通信。举例来说明这个问题:带索引0的线程块中索引为0的线程删除了2的倍数。索引为&gt;的线程块0无法知道这一点。

答案 1 :(得分:2)

在我看来,这段代码存在各种各样的问题。

  1. 您从根本上访问超出范围的项目。在内核中考虑这个序列:

    int tid = threadIdx.x + 1;
    int offset = blockIdx.x * blockDim.x;
    int number = offset + tid;
    
    cache[tid - 1] = global[number];
    

    你(在某些情况下 - 见下文)已经启动了一个与你的global数组完全相同的线程数组。那么当编号最高的线程运行上述代码时会发生什么? number = threadIdx.x+1+blockIdx.x*blockDim.x。此number索引将超出数组末尾。这对于n的许多可能值都是如此。如果您使用proper cuda error checking或已使用cuda-memcheck运行代码,则此问题会很明显。当您在使用CUDA代码时遇到问题并且在向其他人寻求帮助之前,您应该始终做这些事情。

  2. 如果输入n是完美的正方形,则代码才有可能正常工作。其原因包含在这些代码行中(以及内核中的依赖项):

    int n = atol(argv[1]);
    int n_sqrt = floor(sqrt((double)n));
    ...
    int threads = min(n_sqrt, THREADS);
    int blocks = n / threads;
    

    (请注意,此处的正确函数为atoi而不是atol,但我离题了......)除非n是一个完美的正方形,否则结果n_sqrt将有点小于n实际平方根。这将导致您计算小于的总线程数组,而不是必要的大小。 (如果你在这一点上不相信我就没关系。运行我将在下面发布的代码并输入大小如1025,然后查看线程*块的数量是否足以覆盖1025的数组。)< / p>

  3. 如你所说:

      

    每个块检查总sqrt(n)的可能性。

    希望这也指出了非完美平方n的危险,但我们现在必须问“如果n大于最大线程块大小(1024)的平方怎么办?”答案是在许多情况下代码将无法正常工作 - 并且您选择的10240000输入虽然是一个完美的正方形,但是超过1024 ^ 2(1048576)并且由于这个原因它不起作用。您的算法(我声称不是Eratosthenes的Sieve )要求每个块能够检查sqrt(n)可能性,正如您在问题中所述。当由于每个块的线程限制而不再可能时,那么算法开始破解。

  4. 这是一个代码,尝试解决上面的问题#1,并至少解释与#2和#3相关的失败:

    #include <stdio.h>
    #include <stdlib.h>
    #define THREADS 1024
    #define MAX 10240000
    
    #define cudaCheckErrors(msg) \
        do { \
            cudaError_t __err = cudaGetLastError(); \
            if (__err != cudaSuccess) { \
                fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                    msg, cudaGetErrorString(__err), \
                    __FILE__, __LINE__); \
                fprintf(stderr, "*** FAILED - ABORTING\n"); \
                exit(1); \
            } \
        } while (0)
    
    
    __global__ void kernel(int *global, int threads) {
        extern __shared__ int cache[];
    
        int tid = threadIdx.x + 1;
        int offset = blockIdx.x * blockDim.x;
        int number = offset + tid;
    
        if ((blockIdx.x != (gridDim.x-1)) || (threadIdx.x != (blockDim.x-1))){
          cache[tid - 1] = global[number];
          __syncthreads();
    
          int start = offset + 1;
          int end = offset + threads;
    
          for (int i = start; i <= end; i++) {
            if ((i != tid) && (tid != 1) && (i % tid == 0)) {
                cache[i - offset - 1] = 1;
            }
          }
          __syncthreads();
          global[number] = cache[tid - 1];}
    }
    
    
    int cpu_sieve(int n){
        int limit = floor(sqrt(n));
        int *test_arr = (int *)malloc(n*sizeof(int));
        if (test_arr == NULL) return -1;
        memset(test_arr, 0, n*sizeof(int));
        for (int i = 2; i < limit; i++)
          if (!test_arr[i]){
            int j = i*i;
            while (j <= n){
              test_arr[j] = 1;
              j += i;}}
        int count = 0;
        for (int i = 2; i < n; i++)
          if (!test_arr[i]) count++;
        return count;
    }
    
    int main(int argc, char *argv[]) {
        int *array, *dev_array;
        if (argc != 2) {printf("must supply n as command line parameter\n"); return 1;}
        int n = atoi(argv[1]);
        if ((n < 1) || (n > MAX)) {printf("n out of range %d\n", n); return 1;}
        int n_sqrt = floor(sqrt((double)n));
    
        size_t array_size = n * sizeof(int);
        array = (int*) malloc(n * sizeof(int));
        array[0] = 1;
        array[1] = 1;
        for (int i = 2; i < n; i++) {
            array[i] = 0;
        }
    
        cudaMalloc((void**)&dev_array, array_size);
        cudaMemcpy(dev_array, array, array_size, cudaMemcpyHostToDevice);
    
        int threads = min(n_sqrt, THREADS);
        int blocks = n / threads;
        int shared = threads * sizeof(int);
        printf("threads = %d, blocks = %d\n", threads, blocks);
        kernel<<<blocks, threads, shared>>>(dev_array, threads);
        cudaMemcpy(array, dev_array, array_size, cudaMemcpyDeviceToHost);
        cudaCheckErrors("some error");
        int count = 0;
        for (int i = 0; i < n; i++) {
            if (array[i] == 0) {
                count++;
            }
        }
        printf("Count: %d\n", count);
        printf("CPU Sieve: %d\n", cpu_sieve(n));
        return 0;
    }