简单的加法示例:共享内存版本的reduce执行速度比全局内存慢

Simple addition exmple: Shared memory version of reduction performing slower than global memory

本文关键字:内存 执行 reduce 速度 全局 版本 共享 简单      更新时间:2023-10-16

我已经实现了两个版本的add。这两个版本中加法的概念是完全相同的。唯一的区别是,在一个代码中(下面的第一个代码)我使用全局内存,而在第二个代码中我使用共享内存。正如在几个地方提到的,共享内存版本应该更快,但对于我来说,全局内存版本更快。请告诉我哪里做错了注意:我有一个GPU与cc 2.1。因此,对于共享内存,我有32个银行。由于我在示例中只使用了16个int,因此我的代码应该没有银行冲突。请让我知道这是否正确

全球版

#include<stdio.h>
__global__ void reductionGlobal(int* in, int sizeArray, int offset){
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    
    if(tid < sizeArray ){
        if(tid % (offset * 2 ) == 0){
            in[tid] += in[tid+offset];
        }
        
    }
}
int main(){
    int size = 16; // size of present input array. Changes after every loop iteration
    int cidata[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
    int* gidata;
    cudaMalloc((void**)&gidata, size* sizeof(int));
    cudaMemcpy(gidata,cidata, size * sizeof(int), cudaMemcpyHostToDevice);
    int offset = 1; 
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start, 0);
    while(offset < size){
        //use kernel launches to synchronize between different block. syncthreads() will not work
        reductionGlobal<<<4,4>>>(gidata,size,offset);
        offset *=2;
        
    }
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    float elapsedTime; 
    cudaEventElapsedTime(&elapsedTime , start, stop);
    printf("time is %f ms", elapsedTime);
    int* output = (int*)malloc( size * sizeof(int));
    cudaMemcpy(output, gidata, size * sizeof(int), cudaMemcpyDeviceToHost);
    printf("The sum of the array using only global memory is %dn",output[0]);
    getchar();
    return 0;
}

共享内存版本:

#include<stdio.h>
__global__ void computeAddShared(int *in , int *out, int sizeInput){
    extern __shared__ float temp[];
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    int ltid = threadIdx.x;
    temp[ltid] = 0;
    while(tid < sizeInput){
        temp[ltid] += in[tid];
        tid+=gridDim.x * blockDim.x; // to handle array of any size
    }
    __syncthreads();
    int offset = 1;
    while(offset < blockDim.x){
        if(ltid % (offset * 2) == 0){
            temp[ltid] = temp[ltid] + temp[ltid + offset];
        }
        __syncthreads();
        offset*=2;
    }
    if(ltid == 0){
        out[blockIdx.x] = temp[0];
    }
    
}
int main(){
    
    int size = 16; // size of present input array. Changes after every loop iteration
    int cidata[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
    
    int* gidata;
    int* godata;
    cudaMalloc((void**)&gidata, size* sizeof(int));
    cudaMemcpy(gidata,cidata, size * sizeof(int), cudaMemcpyHostToDevice);
    int TPB  = 4;
    int blocks = 10; //to get things kicked off
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start, 0);
    while(blocks != 1 ){
        if(size < TPB){
            TPB  = size; // size is 2^sth
        }
        blocks  = (size+ TPB -1 ) / TPB;
        cudaMalloc((void**)&godata, blocks * sizeof(int));
        computeAddShared<<<blocks, TPB,TPB>>>(gidata, godata,size);
        cudaFree(gidata);
        gidata = godata;
        size = blocks;
    }
    
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    float elapsedTime; 
    cudaEventElapsedTime(&elapsedTime , start, stop);
    printf("time is %f ms", elapsedTime);
    int *output = (int*)malloc(sizeof(int));
    cudaMemcpy(output, gidata, sizeof(int), cudaMemcpyDeviceToHost);
    //Cant free either earlier as both point to same location
    cudaFree(godata);
    cudaFree(gidata);
    printf("The sum of the array is %dn", output[0]);
    getchar();
    return 0;
}

这里有很多错误。首先,一些一般性的注意事项:

  • 你正在对16个数字执行缩减,这是一个荒谬的小的输入尺寸。CUDA在主机上有很多固定的开销还有设备方面。你让设备完成的工作量是如此之小,以至于你所测量的只是那些开销,而不是GPU执行时间。你看到的两个代码之间的区别可能只是由于增加了设置开销,在这种情况下共享内存版本。当然与代码本身无关。如果您想要度量代码的实际性能,那么您为代码所做的工作量必须足够大,以确保执行时间远远大于设置时间。请放心,即使在小型GPU上,您也有大约5个数量级的工作要做。
  • 您提到了银行冲突,但这是您正在使用的体系结构上的稻草人。与旧的硬件相比,Fermi有一个完全不同的共享内存布局,只有一个相对较小的银行冲突问题。在这种情况下当然没什么好担心的。

对于实际的还原码本身:

  • 如果你不能想出减少输入数组的方法对于单个内核启动中每个线程的部分和,那么您真的没有想过关于这个问题的讨论够多了。你目前的做法是在"全球"而"共享"版本效率极低。并行缩减是一个已经解决的问题,CUDA SDK附带了一份关于GPU上的优化和缩减性能的优秀白皮书。你应该在做任何事情之前先看看它。
  • 一旦你达到了每个线程有一个部分和的程度,您希望对每个块执行一个共享内存缩减,以获得每个区块产生一个部分和。这将只需要
  • 你的"共享"版本有一个缓冲区溢出,这应该导致运行时错误。启动时指定的动态共享内存大小时间的单位是字节,而不是单词。如果你的代码有错误检查,你早就发现了。费米有很好的共享存储器保护,如果您尝试写入,它将生成运行时错误