C++ CUDA中不同块和线程的性能优化
我编写了一个程序来计算直方图,其中每个字符字节的256个值都被计算:C++ CUDA中不同块和线程的性能优化,c++,c,cuda,C++,C,Cuda,我编写了一个程序来计算直方图,其中每个字符字节的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__ voi
#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_runtime.h”
#包括“设备启动参数.h”
#包括“.\..\common\book.h”
#包括
#包括
#包括
#定义大小(100*1024*1024)
__全局无效历史内核(无符号字符*缓冲区,长大小,无符号整数*历史){
__共享的无符号整数温度[256];
温度[threadIdx.x]=0;
__同步线程();
int i=threadIdx.x+blockIdx.x*blockDim.x;
int offset=blockDim.x*gridDim.x;
而(i(开发缓冲区、大小、开发历史);
无符号整数历史[256];
cudaMemcpy(历史与发展,历史与发展,256*sizeof(int),cudamemcpydevicetoost);
cudaEventRecord(停止,0);
CUDAEVENTS同步(停止);
浮动时间;
CudaEventReleasedTime(已用时间、开始、停止和结束时间);
printf(“生成时间:%f ms\n”,已用时间);
长和=0;
对于(int i=0;i<256;i++)
总和+=历史[i];
printf(“总和为%ld”,总和);
cudaFree(开发缓冲区);
cudaFree(发展历史);
自由(缓冲);
getch();
返回0;
}
我在《CUDA示例》一书中读到过,根据经验,以多处理机数量两倍的块数启动内核是最理想的解决方案。然而,当我以8倍的块数启动它时,运行时间就缩短了
我运行内核时使用了:1.与多处理器数量相同的块,2.是多处理器数量的两倍的块,3.是4倍的块,依此类推
使用(1),我得到的运行时间是112ms
用(2)我得到的运行时间是73毫秒
我得到的运行时间是52毫秒
有趣的是,在块的数量是多处理器数量的8倍之后,运行时间没有显著变化。就像块是多处理器数量的8倍、256倍和1024倍一样
这怎么解释呢?这种行为很典型。GPU是一个延迟隐藏机器。为了隐藏延迟,当它遇到暂停时,它需要额外的可用新工作。通过给GPU提供大量的块和线程,您可以最大限度地增加可用的额外工作量 一旦您给了它足够的工作来尽可能地隐藏延迟,给它额外的工作并没有帮助。机器饱和了。然而,有额外的工作可用通常也不会造成太大的损害。与块和线程相关的开销很少 通过示例在CUDA中阅读的任何内容可能在特定情况下是正确的,但要启动的正确块数等于多处理器数的两倍肯定不是正确的。更好的目标(通常)是每个多处理器4-8个块 当涉及到块和线程时,通常越多越好,而拥有任意多的块和线程实际上很少会导致性能显著下降。这与典型的CPU线程编程相反,例如,在CPU线程编程中,当超过内核计数时,拥有大量OMP线程可能会导致性能显著降低
当您调整代码以获得最后10%的性能时,您将看到人们将他们启动的块数量限制为SMs数量的4-8倍,并构建他们的线程块以在数据集上循环。但在大多数情况下,这通常只会带来几%的性能改进。作为一个合理的CUDA编程起点,至少要针对上万个线程和数百个块。一个经过仔细调整的代码可能会用更少的块和线程使机器饱和,但在这一点上它将变得依赖于GPU。正如我已经说过的,拥有数百万个线程和数千个块几乎不会对性能造成太大的损害。这种行为是典型的。GPU是一个延迟隐藏机器。为了隐藏延迟,当它遇到暂停时,它需要额外的可用新工作。通过给GPU提供大量的块和线程,您可以最大限度地增加可用的额外工作量 一旦您给了它足够的工作来尽可能地隐藏延迟,给它额外的工作并没有帮助。机器饱和了。然而,有额外的工作可用通常也不会造成太大的损害。与块和线程相关的开销很少 通过示例在CUDA中阅读的任何内容可能在特定情况下是正确的,但要启动的正确块数等于多处理器数的两倍肯定不是正确的。更好的目标(通常)是每个多处理器4-8个块 当涉及到块和线程时,通常越多越好,而拥有任意多的块和线程实际上很少会导致性能显著下降。这与典型的CPU线程编程相反,例如,在CPU线程编程中,当超过内核计数时,拥有大量OMP线程可能会导致性能显著降低 当您为性能的最后10%调整代码时,您将看到人们限制块的数量