增加CUDA中每个线程的工作量的示例

简介 :首先,作为一个介绍,我很自豪地问我关于StackOverflow的第一个问题。 我希望我能够帮助其他人,就像他们帮助我一样。

算法

我正在用CUDA编写一个程序,问题如下:

  • 两个矩阵A(n * 128)和B(m * 128)

  • 我拿A的第一行,然后我逐一计算该向量和B的所有行之间的距离。

  • 我将每个距离的结果写入矩阵C的一行,因此C的元素C(i,j)包含A的行i和行B的距离。

  • 我继续下一行A.

  • 我已经这样实现了:我有一个由(n * m)块组成的网格,每块有128个线程。 (1 * 128)。

    问题 :程序运行成功,预期的结果是,但时间执行速度仅比单线程CPU版本快5到10倍。 所以我想知道如何在减少之前增加每个线程的工作量以提高性能

    内核代码(原始:未优化)

     __global__ void EuclideanDistances( float *A, float *B , float *C , int n , int m)
    {
        // SIZE is equal to 128
    __shared__ float accumResult[SIZE];
    float sA;
    float sB;
    
        // MAPPING
    int bx = blockIdx.x;  // n
    int by = blockIdx.y;  // m
    int ty = threadIdx.y; // 128
    int tx = threadIdx.x; // 1
    
    
    sA = A [bx * SIZE + ty];
    sB = B [by * SIZE + ty];
    __syncthreads();
    
    
    accumResult[ty] = (sA - sB) * (sA - sB);
    __syncthreads();
    
    
    // Parallel tree-reduction
    for (int stride = SIZE/2 ; stride > 0 ; stride >>= 1)
        if (ty < stride)
        {
            accumResult[ty] += accumResult [stride + ty];
              __syncthreads();
        }
    
        // Writing results to output matrix
    if ((threadIdx.y == 0))
        C [bx * m + by] = accumResult[ty];
           __syncthreads();
    }
    

    UPDATE

    现在,我使用的另一个映射:而不是采取的网格nm块和块128的线程,我为了减少块的数目增加一个块内的线程数。

    新的映射:

    的块1288螺纹(总共1024个线程,这是最大尺寸)

    n/8m/8块的网格

    不幸的是,它给出了错误的结果)。

    优化的内核代码(待更新)

    __global__ void EuclideanDistances( float *A, float *B , float *C, int n , int m)
    {
        __shared__ float accumResult[SIZE][8];
    __shared__ float sA[SIZE][8];
    __shared__ float sB[SIZE][8];
    
    int bx = blockIdx.x;  // n / 8
    int by = blockIdx.y;  // m / 8
    int tx = threadIdx.x; // 8
    int ty = threadIdx.y; // 128
    int i = bx * tx * SIZE + ty;
    int j = by * tx * SIZE + ty;
    
    sA[ty][tx] = A [i];
    sB[ty][tx] = B[j];
    __syncthreads();
    
    
    accumResult[ty][tx] = (sA[ty][tx] - sB[ty][tx]) * (sA[ty][tx] - sB[ty][tx]);
    __syncthreads();
    
    // Reduction
    for (int stride = SIZE/2 ; stride > 0 ; stride>>=1)
        if (ty < stride)
        {
            accumResult[ty][tx] += accumResult [stride + ty][tx];
            __syncthreads();
        }
    
        C[bx *  m + by] = accumResult[0][tx];
    }
    

    主机代码(分配+内核调用)

        int main()
    {
         int m = 20000; //MatrixA size : m * SIZE
         int n = 4000;  //MatrixB size : n * SIZE
    
         srand((unsigned)time(0));
    
         // Host Allocations
         float *matrixA = (float *) malloc (n * SIZE * sizeof(float));
         for(int i=0; i < n * SIZE; i++)
             matrixA[i] = (float) (rand()%100)+1;
    
         float *matrixB = (float *) malloc (m * SIZE * sizeof(float));
         for(int i=0; i < m * SIZE; i++)
             matrixB[i] = (float) (rand()%100)+1;
    
         float *results_kernel1 = (float *) malloc (n * m * sizeof(float));
         float *results_kernel2 = (float *) malloc (n * m * sizeof(float));
    
    
         //Device Allocation
         float *d_matrixA;
         float *d_matrixB;
         cudaMalloc((void **)&d_matrixA, n * SIZE * sizeof(float));
         cudaMalloc((void **)&d_matrixB, m * SIZE * sizeof(float));
         cudaMemcpy(d_matrixA , matrixA , n * SIZE * sizeof(float) , cudaMemcpyHostToDevice);
         cudaMemcpy(d_matrixB , matrixB , m * SIZE * sizeof(float) , cudaMemcpyHostToDevice);
    
         float *d_results_kernel1;
         float *d_results_kernel2;
         cudaMalloc((void **)&d_results_kernel1 , n * m * sizeof(float));
         cudaMalloc((void **)&d_results_kernel2 , n * m * sizeof(float));
    
         dim3 threads1 (1 , 128);
         dim3 blocks1  (n , m);
         EuclideanDistances1 <<<blocks1 , threads1>>> (d_matrixA , d_matrixB , d_results_kernel1 , n , m);
         cudaDeviceSynchronize();
         cudaMemcpy(results_kernel1 , d_results_kernel1 , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         cudaFree(d_results_kernel1);
    
         dim3 threads2 (8 , 128);   // 1024 threads per block (maximum)
         dim3 blocks2  (ceil((float)n/8) , ceil((float)m/8));
         EuclideanDistances2 <<<blocks2 , threads2>>> (d_matrixA , d_matrixB , d_results_kernel2 , n , m);
         cudaDeviceSynchronize();
         cudaMemcpy(results_kernel2 , d_results_kernel2 , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         cudaFree(d_results_kernel2);
    
         // Visualising and comparing results
         for (int i = 0 ; i < 50 ; i++)
             std::cout << "kernel1 : " << results_kernel1[i] << "  |  kernel2 : " << results_kernel2[i] << std::endl;
    
         free(matrixA);
         free(matrixB);
         free(results_kernel1);
         free(results_kernel2);
    
         return 0;
    }
    

    PS :我有NVIDIA GTX 650(计算能力3.0)的CUDA 6.0,


    看来你的问题有两个组成部分:

  • 为什么我的第二个内核不工作?
  • 我如何让代码运行得更快?
  • 为什么我的第二个内核不工作?

    你有几个问题:

  • 索引ij初始计算中的问题以及存储C值的索引。
  • 违反_syncthreads()在条件块内的使用
  • 项目1是获得代码工作的关键要素。

    如何让我的代码更快运行?

    这更涉及。 首先,“增加每个线程的工作量”的尝试没有做任何事情,只是每个块的线程数量增加(从128到8 * 128)。 每个线程的工作量大致相同。 此外,在进行这个尝试的2D线程块的过程中,我相信发生了一些不好的事情:

  • 各种合并和共享内存银行冲突的加载和存储模式被打破。
  • 由于每块所需的共享内存量,有效占用率下降了。
  • 第二个内核的净效果是将执行时间加倍。 所以这不是我们想要的。

    然而,增加每个线程的工作量可能是一个好主意,同时使用共享内存,并尝试保持良好(全局,共享)内存访问模式,并允许增加占用率。

    接下来是这方面的一项工作。 下面的代码已经修复了您的第二个内核,以及定时基础架构,以及完整的数据验证以及两个新内核。 第一个新内核(#3)就是我称之为“天真”的内核。 它只是为每个输出点分配一个线程,并且每个线程循环遍历必要的向量,计算其各自的结果。 不使用共享内存,甚至不关注合并或任何其他优化。 然而,通过调整线程块配置(16,16) - >(8,32)线程,我从@talonmies的回答(现在删除)中观察到,该内核执行速度比“快速”内核快3倍。 在进一步思考(8,32)观察之后,我得出结论认为,下一次尝试优化应重点关注:

  • 消除并行约简的使用以计算向量距离(即,允许相邻的线程使用直的for-loop循环遍历向量)
  • 从缓存中获益的最大化
  • 有效使用共享内存
  • 坚持完美的全球联合/完美共享内存的使用所有读取和写入
  • 第4项在评论中提出了问题:“我可以调换矩阵吗?” 有了这项许可,就可以重新组织数据以促进上述第4项。 上面的项目2在我的“快速”内核(#4)中通过将B向量加载到共享内存中来解决,同时允许高速缓存主要集中于高速缓存A向量,希望减少高速缓存抖动(A是2矢量阵列,大约2MB - 费米L2是768K,开普勒L2是1.5MB)。 通过以转置形式提供A并有效地将片上B从共享存储器“转置”,可以使用直接for-loop来计算向量距离,同时允许相邻线程完全合并读取和写入,以及“有效”使用共享内存(即非银行冲突负载和广播读取)。

    在我的特定时机(Quadro5000 cc2.0 GPU,CUDA 6,RHEL 5.5),我发现你的“快速”内核需要2秒钟,我的“幼稚”内核需要大约0.7秒,而我的“快速”内核需要大约0.2秒,虽然转置了(A,C)数据。

    编辑:我做了一个额外的优化,即每个块一次计算多个( CHKSIZE )B向量。 您可以将CHKSIZE设置为1以查看以前的结果(〜0.2秒)。 我发现4的CHKSIZE给了很好的改进。 这是试图利用A的数据重用的攻击。在CHKSIZE为4的情况下进行此额外优化时,内核4的内核时间下降到大约0.1秒。

    以下是代码和示例运行:

    $ cat t460.cu 
    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    // both M and N must be evenly divisible by SIZE, M must be evenly divisible by CHKSIZE
    #define SIZE 128
    #define N 4000
    #define M 20000
    #define CHKSIZE 4
    
     __global__ void EuclideanDistances1( float *A, float *B , float *C , int n , int m)
    {
        // SIZE is equal to 128
    __shared__ float accumResult[SIZE];
    float sA;
    float sB;
    
        // MAPPING
    int bx = blockIdx.x;  // n
    int by = blockIdx.y;  // m
    int ty = threadIdx.y; // 128
    //int tx = threadIdx.x; // 1
    
    sA = A [bx * SIZE + ty];
    sB = B [by * SIZE + ty];
    __syncthreads();
    
    accumResult[ty] = (sA - sB) * (sA - sB);
    __syncthreads();
    
    // Parallel tree-reduction
    for (int stride = SIZE/2 ; stride > 0 ; stride >>= 1){
        if (ty < stride)
        {
            accumResult[ty] += accumResult [stride + ty];
        }
              __syncthreads();
      }
    
        // Writing results to output matrix
    if ((ty == 0))
        C [bx * m + by] = accumResult[ty];
           __syncthreads();
    }
    
    __global__ void EuclideanDistances2( float *A, float *B , float *C, int n , int m)
    {
    __shared__ float accumResult[SIZE][8];
    __shared__ float sA[SIZE][8];
    __shared__ float sB[SIZE][8];
    
    int bx = blockIdx.x;  // n / 8
    int by = blockIdx.y;  // m
    int tx = threadIdx.x; // 8
    int ty = threadIdx.y; // 128
    int i = ((bx*8) + tx) * SIZE + ty;
    int j = by * SIZE + ty;
    
    sA[ty][tx] = A[i];
    sB[ty][tx] = B[j];
    __syncthreads();
    
    accumResult[ty][tx] = (sA[ty][tx] - sB[ty][tx]) * (sA[ty][tx] - sB[ty][tx]);
    __syncthreads();
    
    // Reduction
    for (int stride = SIZE/2 ; stride > 0 ; stride>>=1){
        if (ty < stride)
        {
            accumResult[ty][tx] += accumResult [stride + ty][tx];
        }
        __syncthreads();
      }
    
    if (ty == 0)
        C[((bx*8)+tx) *  m + by] = accumResult[0][tx];
    }
    //naive kernel
    __global__ void EuclideanDistances3( float *A, float *B , float *C, int n , int m){
      int idx = threadIdx.x+blockDim.x*blockIdx.x;
      int idy = threadIdx.y+blockDim.y*blockIdx.y;
      float result = 0.0f;
    
      if ((idx < n) && (idy < m)){
        for (int i = 0; i < SIZE; i++){
          float temp = A[(idx*SIZE)+i] - B[(idy*SIZE)+i];
          result += temp * temp;}
        C[(idx*m) + idy] = result;
      }
    }
    //optimized kernel
    __global__ void EuclideanDistances4( const float *A, const float *B , float *C, const int n , const int m){
      // n, A,  4000 this kernel assumes A is column-major A(SIZE, n)
      // m, B, 20000 this kernel assumes B is row-major    B(m, SIZE)
      // this kernel assumes C is column-major             C(m,n)
      // this kernel assumes number of threads per threadblock == SIZE
      // CHKSIZE is the number of B vectors that will be compute per block
      __shared__ float my_sB[CHKSIZE*SIZE];  // enough shared storage for CHKSIZE vectors of B
      int bx  = blockIdx.x; // one block per CHKSIZE rows of B (the larger input matrix)
      while ((bx*CHKSIZE) < m){ // not used, this while loop could be used to extend a block to multiple chunks
        int tx  = threadIdx.x;
        for (int i = 0; i < CHKSIZE; i++)  // load vectors of B into shared memory
          my_sB[(i*SIZE)+tx] = B[(((bx*CHKSIZE)+i)*SIZE)+tx];
        __syncthreads();
        while (tx < n){  //loop across all vectors in A
          float result[CHKSIZE];
          for (int i = 0; i < CHKSIZE; i++)
            result[i] = 0.0f;
          for (int i = 0; i < SIZE; i++){
            float Atemp = A[(n*i)+tx];
            for (int j = 0; j < CHKSIZE; j++){ // compute all CHKSIZE B vectors with read of A
              float temp = Atemp - my_sB[i + (j*SIZE)];
              result[j] += temp * temp;}}
          for (int i = 0; i < CHKSIZE; i++) // store CHKSIZE results
            C[((i+(bx*CHKSIZE))*n)+ tx] = result[i];
          tx += blockDim.x;  } // continue looping across vectors in A
        __syncthreads(); // necessary to prevent warps from racing ahead, if block looping is used
        bx += gridDim.x;}
    }
    
    float comp_euclid_sq(const float *rA, const float *rB, const int size){
    
      float result = 0.0f;
      float temp;
      for (int i = 0; i < size; i++){
        temp = (rA[i] - rB[i]);
        result += temp * temp;}
      return result;
    }
    
    int main()
    {
         float et1=0.0f, et2=0.0f, et3=0.0f, et4=0.0f;
         cudaEvent_t start1, start2, start3,start4, stop1, stop2, stop3, stop4;
         cudaEventCreate(&start1);
         cudaEventCreate(&start2);
         cudaEventCreate(&start3);
         cudaEventCreate(&start4);
         cudaEventCreate(&stop1);
         cudaEventCreate(&stop2);
         cudaEventCreate(&stop3);
         cudaEventCreate(&stop4);
    
         int n = N;  //MatrixA size : n * SIZE
         int m = M; //MatrixB size : m * SIZE
    
         srand((unsigned)time(0));
    
         // Host Allocations
         float *matrixA = (float *) malloc (n * SIZE * sizeof(float));
         for(int i=0; i < n * SIZE; i++)
             matrixA[i] = (float) (rand()%100)+1;
    
         float *matrixB = (float *) malloc (m * SIZE * sizeof(float));
         for(int i=0; i < m * SIZE; i++)
             matrixB[i] = (float) (rand()%100)+1;
    
         float *results_kernel = (float *) malloc (n * m * sizeof(float));
         float *cpu_results_kernel = (float *) malloc (n * m * sizeof(float));
         for (int i = 0; i< n*m; i++)
           cpu_results_kernel[i] = comp_euclid_sq(matrixA + ((i/m)*SIZE), matrixB + (i%m)*SIZE, SIZE);
    
         //Device Allocation
         float *d_matrixA;
         float *d_matrixB;
         cudaMalloc((void **)&d_matrixA, n * SIZE * sizeof(float));
         cudaMalloc((void **)&d_matrixB, m * SIZE * sizeof(float));
         cudaMemcpy(d_matrixA , matrixA , n * SIZE * sizeof(float) , cudaMemcpyHostToDevice);
         cudaMemcpy(d_matrixB , matrixB , m * SIZE * sizeof(float) , cudaMemcpyHostToDevice);
    
         float *d_results_kernel;
         cudaMalloc((void **)&d_results_kernel , n * m * sizeof(float));
    
    
         dim3 threads1 (1 , SIZE);
         dim3 blocks1  (n , m);
         cudaEventRecord(start1);
         EuclideanDistances1 <<<blocks1 , threads1>>> (d_matrixA , d_matrixB , d_results_kernel , n , m);
         cudaEventRecord(stop1);
         cudaMemcpy(results_kernel , d_results_kernel , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         for (int i = 0; i< n*m; i++) {
           if (results_kernel[i] != cpu_results_kernel[i])  {printf("cpu/kernel1 mismatch at %d, cpu: %f, kernel1: %fn", i, cpu_results_kernel[i], results_kernel[i]); return 1;}}
         cudaMemset(d_results_kernel, 0, n*m*sizeof(float));
         cudaEventSynchronize(stop1);
         cudaEventElapsedTime(&et1, start1, stop1);
    
         dim3 threads2 (8 , SIZE);   // 1024 threads per block (maximum)
         dim3 blocks2  (n/8 , m); // assumes n evenly divisible by 8
         cudaEventRecord(start2);
         EuclideanDistances2 <<<blocks2 , threads2>>> (d_matrixA , d_matrixB , d_results_kernel , n , m);
         cudaEventRecord(stop2);
         cudaMemcpy(results_kernel , d_results_kernel , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         for (int i = 0; i< n*m; i++) {
           if (results_kernel[i] != cpu_results_kernel[i])  {printf("cpu/kernel2 mismatch at %d, cpu: %f, kernel1: %fn", i, cpu_results_kernel[i], results_kernel[i]); return 1;}}
         cudaMemset(d_results_kernel, 0, n*m*sizeof(float));
         cudaEventSynchronize(stop2);
         cudaEventElapsedTime(&et2, start2, stop2);
    
         cudaFuncSetCacheConfig(EuclideanDistances3, cudaFuncCachePreferL1);
         dim3 threads3 (8, 32);   // 1024 threads per block (maximum)
         dim3 blocks3  (n/threads3.x , m/threads3.y); // assumes evenly divisible
         cudaEventRecord(start3);
         EuclideanDistances3 <<<blocks3 , threads3>>> (d_matrixA , d_matrixB , d_results_kernel , n , m);
         cudaEventRecord(stop3);
         cudaMemcpy(results_kernel , d_results_kernel , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         for (int i = 0; i< n*m; i++) {
           if (results_kernel[i] != cpu_results_kernel[i])  {printf("cpu/kernel3 mismatch at %d, cpu: %f, kernel3: %fn", i, cpu_results_kernel[i], results_kernel[i]); return 1;}}
         cudaMemset(d_results_kernel, 0, n*m*sizeof(float));
         cudaEventSynchronize(stop3);
         cudaEventElapsedTime(&et3, start3, stop3);
    
         // transpose matrix A
         float *matrixA_T = (float *) malloc (n * SIZE * sizeof(float));
           for (int i = 0; i < n; i++)
             for (int j = 0; j < SIZE; j++)
               matrixA_T[(j*n)+i] = matrixA[(i*SIZE)+j];
         cudaMemcpy(d_matrixA , matrixA_T , n * SIZE * sizeof(float) , cudaMemcpyHostToDevice);
    
         cudaFuncSetCacheConfig(EuclideanDistances4, cudaFuncCachePreferL1);
         dim3 threads4(SIZE); // one thread per vector element
         dim3 blocks4(m/CHKSIZE);
         cudaEventRecord(start4);
         EuclideanDistances4 <<<blocks4 , threads4>>> (d_matrixA , d_matrixB , d_results_kernel , n , m);
         cudaEventRecord(stop4);
         cudaMemcpy(results_kernel , d_results_kernel , n * m *sizeof(float) , cudaMemcpyDeviceToHost);
         // test for correct transposed result C(m,n)
         for (int i = 0; i< n; i++)
           for (int j = 0; j < m; j++)
             if (results_kernel[(j*n)+i] != cpu_results_kernel[(i*m)+j])  {printf("cpu/kernel4 mismatch at %d,%d, cpu: %f, kernel4: %fn", i,j, cpu_results_kernel[(i*m)+j], results_kernel[(j*n)+i]); return 1;}
         cudaEventSynchronize(stop4);
         cudaEventElapsedTime(&et4, start4, stop4);
         cudaFree(d_results_kernel);
    
         printf("Success!n");
         printf("kernel1 : %.fms, kernel2 : %.fms, kernel3 : %.fms, kernel4 : %.fmsn", et1, et2, et3, et4);
    
         free(matrixA);
         free(matrixB);
         free(results_kernel);
    
         return 0;
    }
    
    $ nvcc -O3 -arch=sm_20 -o t460 t460.cu
    $ ./t460
    Success!
    kernel1 : 2213ms, kernel2 : 4660ms, kernel3 : 691ms, kernel4 : 99ms
    $
    

    希望这会让你有更多的想法来开展工作。 您的cc3.0设备上可能会有不同的时间。

    是否可以进一步优化? 大概。 我要研究的第一个目标是找出如何利用向量A上的数据重用机会(向量B的​​数据重用已经在内核4中通过将其加载到共享内存中进行处理)。可以使用某些共享内存来存储A部分以使代码运行更快。)

    我想我还应该提到,在你提供的代码的引导之后,这个代码正在计算欧几里德距离的平方。 对内核的微小修改可以使其计算实际的欧氏距离( C[...] = sqrtf(...); )然而,我所包含的验证假设结果是“在范围内”在float存储整数量。 你的测试用例满足这个要求,否则验证代码需要修改(如果使用了sqrtf )。

    链接地址: http://www.djcxy.com/p/20937.html

    上一篇: Example of increasing the work per thread in CUDA

    下一篇: Jquery Scroller needs to stop scrolling after reaching end