我编写了一个计算直方图的程序,其中计算了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倍一样。
如何解释?
答案 0 :(得分:4)
这种行为很典型。 GPU是一种延迟隐藏机器。为了隐藏延迟,当它遇到停顿时,它需要额外的新工作。您可以通过为GPU提供大量的块和线程来最大化额外的新工作量。
一旦你给了它足够的工作来尽可能地隐藏延迟,给它额外的工作并没有帮助。机器已经饱和。然而,具有额外的可用工作通常/通常也没有太大的损害。与块和线程相关的开销很小。
无论您在CUDA中通过示例阅读的内容对于特定情况都是如此,但启动的正确块数等于多处理器数量的两倍肯定不正确。更好的目标(通常)是每个多处理器4-8个块。
当涉及到块和线程时,通常会更好,并且很少会出现具有任意大量块和线程的情况,实际上会导致性能显着下降。这与典型的CPU线程编程相反,例如,当您超过核心数时,拥有大量OMP线程可能会导致性能显着下降。
当您调整代码的最后10%的性能时,您会看到人们限制他们启动的块数量,通常是SM数量的4-8倍,并构造他们的线程块循环遍历数据集。但在大多数情况下,这通常只会使性能提高几个百分点。作为一个合理的CUDA编程起点,至少要瞄准数万个线程和数百个块。仔细调整的代码可能能够用更少的块和线程使机器饱和,但在那时它将依赖于GPU。正如我已经说过的那样,对于拥有数百万个线程和数千个块,很少会对性能造成损害。