为什么增加cuda中的块数会增加时间?

为什么增加cuda中的块数会增加时间?,cuda,Cuda,我的理解是,在CUDA中,增加块的数量不会增加时间,因为它们是并行实现的,但是在我的代码中,如果我将块的数量加倍,时间也会加倍 #include <cuda.h> #include <curand.h> #include <curand_kernel.h> #include <stdio.h> #include <stdlib.h> #include <iostream> #define num_of_blocks 500

我的理解是,在CUDA中,增加块的数量不会增加时间,因为它们是并行实现的,但是在我的代码中,如果我将块的数量加倍,时间也会加倍

#include <cuda.h>
#include <curand.h>
#include <curand_kernel.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

#define num_of_blocks 500
#define num_of_threads 512

__constant__ double y = 1.1;

// set seed for random number generator
__global__ void initcuRand(curandState* globalState, unsigned long seed){
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    curand_init(seed, idx, 0, &globalState[idx]);
}

// kernel function for SIR
__global__ void test(curandState* globalState, double *dev_data){
    // global threads id
    int idx     = threadIdx.x + blockIdx.x * blockDim.x;

    // local threads id
    int lidx    = threadIdx.x;

    // creat shared memory to store seeds
    __shared__ curandState localState[num_of_threads];

    // shared memory to store samples
    __shared__ double sample[num_of_threads];

    // copy global seed to local
    localState[lidx]    = globalState[idx];
    __syncthreads();

    sample[lidx]    =  y + curand_normal_double(&localState[lidx]);

    if(lidx == 0){
        // save the first sample to dev_data;
        dev_data[blockIdx.x] = sample[0];
    }

    globalState[idx]    = localState[lidx];
}

int main(){
    // creat random number seeds;
    curandState *globalState;
    cudaMalloc((void**)&globalState, num_of_blocks*num_of_threads*sizeof(curandState));
    initcuRand<<<num_of_blocks, num_of_threads>>>(globalState, 1);

    double *dev_data;
    cudaMalloc((double**)&dev_data, num_of_blocks*sizeof(double));

    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    // Start record
    cudaEventRecord(start, 0);

    test<<<num_of_blocks, num_of_threads>>>(globalState, dev_data);

    // Stop event
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    float elapsedTime;
    cudaEventElapsedTime(&elapsedTime, start, stop); // that's our time!
    // Clean up:
    cudaEventDestroy(start);
    cudaEventDestroy(stop);

    std::cout << "Time ellapsed: " << elapsedTime << std::endl;

    cudaFree(dev_data);
    cudaFree(globalState);
    return 0;
}

那么,时间增加的原因是什么?是因为我访问常量内存还是因为我将数据从共享内存复制到全局内存?这是优化它的一些方法吗?

虽然能够并行运行的块的数量可能很大,但由于芯片资源有限,它仍然是有限的。如果内核启动中请求的块数超过该限制,则任何其他块都必须等待较早的块完成并释放其资源

一个有限的资源是共享内存,您的内核使用28千字节。CUDA 8.0兼容的Nvidia GPU为每个流式多处理器(SM)提供48到112千字节的共享内存,因此每次运行的最大块数在GPU上SMs数的1到3倍之间

其他有限资源是调度器中的寄存器和各种每扭曲资源。是一个方便的Excel电子表格(也适用于OpenOffice/LibreOffice),它向您展示了这些资源如何限制特定内核的每个SM的块数。编译内核,将选项
--ptxas options=“-v”
添加到
nvcc
命令行中,找到写有“ptxas info:Used XX registers,YY bytes smem,zz bytes cmem[0],ww bytes cmem[2]”的行,然后输入XX,YY,您尝试启动的每个块的线程数,并将GPU的计算能力输入电子表格。然后,它将显示一个SM上可以并行运行的最大块数

您没有提到您一直在运行测试的GPU,因此我将使用GTX980作为示例。它有16条SMs,每条共享内存为96Kb,因此最多可以并行运行16×3=48个块。如果没有使用共享内存,最大驻留扭曲数会将每个SM的块数限制为4,从而允许64个块并行运行


在任何现有的Nvidia GPU上,您的示例都需要至少十几个按顺序执行的块,这解释了为什么块数增加一倍也会使运行时间增加一倍。

虽然可以并行运行的块数可能很大,但由于片内资源有限,它仍然是有限的。如果内核启动中请求的块数超过该限制,则任何其他块都必须等待较早的块完成并释放其资源

一个有限的资源是共享内存,您的内核使用28千字节。CUDA 8.0兼容的Nvidia GPU为每个流式多处理器(SM)提供48到112千字节的共享内存,因此每次运行的最大块数在GPU上SMs数的1到3倍之间

其他有限资源是调度器中的寄存器和各种每扭曲资源。是一个方便的Excel电子表格(也适用于OpenOffice/LibreOffice),它向您展示了这些资源如何限制特定内核的每个SM的块数。编译内核,将选项
--ptxas options=“-v”
添加到
nvcc
命令行中,找到写有“ptxas info:Used XX registers,YY bytes smem,zz bytes cmem[0],ww bytes cmem[2]”的行,然后输入XX,YY,您尝试启动的每个块的线程数,并将GPU的计算能力输入电子表格。然后,它将显示一个SM上可以并行运行的最大块数

您没有提到您一直在运行测试的GPU,因此我将使用GTX980作为示例。它有16条SMs,每条共享内存为96Kb,因此最多可以并行运行16×3=48个块。如果没有使用共享内存,最大驻留扭曲数会将每个SM的块数限制为4,从而允许64个块并行运行


在任何现有的Nvidia GPU上,您的示例都需要至少十几个按顺序执行的块,这解释了为什么块数翻倍也会使运行时间翻倍。

感谢您的帮助。我使用的GPU是特斯拉K80,在那里我可以得到它拥有的流式多处理器的数量?此外,它仅适用于一个GPU,如果我使用两个GPU来运行代码,这是否有意义,即每个GPU可以同时运行的最大块数,并且我们有更多的GPU,时间会增加吗?对于特斯拉产品线,这一信息比GeForce卡更难获得,对于后者,您可以在他们的网站上查找规格。我从来没有找到过类似的专业卡信息(我从来都不太明白为什么——英伟达网站上的特斯拉营销材料似乎是针对做出购买决定的经理的,显然是假设他们无法阅读或理解技术规格)。这句话放在一边,您可以通过结合每个GPU和每个GPU的事实来获得信息,以发现K80上的两个GPU中的每一个都有13条SMs。但是,工作不会自动在K80上的两个GPU之间分配。您必须自己分割工作,在每个GPU上显式启动一个内核,每个内核都是您想要运行的块数的一半。您还必须将所有支持数据复制到两个GPU,因为它们不共享公共地址空间(它们实际上是单个印刷电路板上的独立设备)。感谢您提供此信息。我在K80上测试了16个内核,这与我启动2个模块的时间相同。事实上,在我的代码中,共享内存已被充分利用,即使我无法启动512个线程。我还有一个小测验,如果我在不同的GPU上启动更多块,它们之间的通信会花费很长时间吗?谢谢你的帮助。我使用的GPU是特斯拉K80,在那里我可以获得流媒体mult的数量
number of blocks: 500, Time ellapsed: 0.39136.
number of blocks: 1000, Time ellapsed: 0.618656.