使用CUDA中的不同块和线程进行性能优化

时间:2014-12-14 19:02:55

标签: c++ c cuda

我编写了一个计算直方图的程序,其中计算了char字节的256个值中的每一个:

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "..\..\common\book.h"
#include <stdio.h> 
#include <cuda.h>
#include <conio.h>

#define SIZE (100*1024*1024)

__global__ void histo_kernel(unsigned char *buffer, long size, unsigned int *histo){

__shared__ unsigned int temp[256];
temp[threadIdx.x] = 0;
__syncthreads();

int i = threadIdx.x + blockIdx.x * blockDim.x;
int offset = blockDim.x * gridDim.x;

while (i < size) {
    atomicAdd(&temp[buffer[i]], 1);
    i += offset;}

__syncthreads();
atomicAdd(&(histo[threadIdx.x]), temp[threadIdx.x]);
}

int main()
{


unsigned char *buffer = (unsigned char*)big_random_block(SIZE);

cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);

unsigned char *dev_buffer;
unsigned int *dev_histo;
cudaMalloc((void**)&dev_buffer, SIZE);
cudaMemcpy(dev_buffer, buffer, SIZE, cudaMemcpyHostToDevice);

cudaMalloc((void**)&dev_histo, 256 * sizeof(long));
cudaMemset(dev_histo, 0, 256 * sizeof(int));

cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
int blocks = prop.multiProcessorCount; 

histo_kernel << <blocks * 256 , 256>> >(dev_buffer, SIZE, dev_histo);

unsigned int histo[256];
cudaMemcpy(&histo, dev_histo, 256 * sizeof(int), cudaMemcpyDeviceToHost);

cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsed_time;
cudaEventElapsedTime(&elapsed_time, start, stop);
printf("Time to generate: %f ms\n", elapsed_time);

long sum = 0;
for (int i = 0; i < 256; i++)
    sum += histo[i];

printf("The sum is %ld", sum);

cudaFree(dev_buffer);
cudaFree(dev_histo);
free(buffer);

getch();
return 0;

}

我在书中通过示例读取CUDA,根据经验发现,启动内核的块数是两倍多处理器的数量,这是最优的解决方案。然而,当我以8倍的块数启动它时,运行时间会缩短。

我使用以下命令运行内核:1.Blocks与多处理器的数量相同,2.Blocks是多处理器数量的两倍,3.Blocks是4次,依此类推。

使用(1),我的运行时间为112ms 随着(2)我的运行时间为73毫秒 (3)我的运行时间为52ms 有趣的是,在块数为多处理器数量的8倍之后,运行时间没有显着变化。就像块是8倍,256倍和多倍处理器数量的1024倍一样。

如何解释?

1 个答案:

答案 0 :(得分:4)

这种行为很典型。 GPU是一种延迟隐藏机器。为了隐藏延迟,当它遇到停顿时,它需要额外的新工作。您可以通过为GPU提供大量的块和线程来最大化额外的新工作量。

一旦你给了它足够的工作来尽可能地隐藏延迟,给它额外的工作并没有帮助。机器已经饱和。然而,具有额外的可用工作通常/通常也没有太大的损害。与块和线程相关的开销很小。

无论您在CUDA中通过示例阅读的内容对于特定情况都是如此,但启动的正确块数等于多处理器数量的两倍肯定不正确。更好的目标(通常)是每个多处理器4-8个块。

当涉及到块和线程时,通常会更好,并且很少会出现具有任意大量块和线程的情况,实际上会导致性能显着下降。这与典型的CPU线程编程相反,例如,当您超过核心数时,拥有大量OMP线程可能会导致性能显着下降。

当您调整代码的最后10%的性能时,您会看到人们限制他们启动的块数量,通常是SM数量的4-8倍,并构造他们的线程块循环遍历数据集。但在大多数情况下,这通常只会使性能提高几个百分点。作为一个合理的CUDA编程起点,至少要瞄准数万个线程和数百个块。仔细调整的代码可能能够用更少的块和线程使机器饱和,但在那时它将依赖于GPU。正如我已经说过的那样,对于拥有数百万个线程和数千个块,很少会对性能造成损害。