嵌套循环,在CUDA C ++中具有内部循环的可变范围

时间:2016-12-17 14:11:29

标签: cuda

我需要与CUDA并行化一个嵌套循环:

for(int ix=0; ix<Nx; ix++) {
   for(int iy=0; iy<Ny[ix]; iy++) {
      SomeFunction(ix, iy);
      ...

其中Ny[]是CUDA全局内存中的数组。这个循环在一次运行中被调用多次,Nx并且Ny[]的元素在不同的调用中发生变化,并且它们可能很大(Nx从0到大约1百万,{{ 1}}从0到大约10000)。 原则上我可以使用CUDA内核,其中线程排列在由NyNx*max(Ny)索引的大小为ix的二维网格中,因此内核的计算成本为{{1 }}。问题在于,在我的情况下,iy可能比O[Nx*max(Ny)/Ncores]的平均值大一个数量级。在某些调用中,max(Ny)的一些元素可能非常大(比如1000左右),而其他大多数元素都非常小。 我希望上面循环的良好并行实现具有Ny的计算成本,在我的情况下会比Ny小得多,但我不知道我这样做。我有一个模糊的想法,一个好的解决方案可以基于首先排序O[Nx*average(Ny)/Ncores]的元素。 仅用于测试目的,这里是一段代码,它产生的Ny值与我的分布相似。

O[Nx*max(Ny)/Ncores]

1 个答案:

答案 0 :(得分:1)

如果SomeFunction比简单的加载和存储计算成本更高,我会考虑创建一个int2数组,它可以存储SomeFunction()的执行参数。然后将此数组复制到设备并运行一个内核,该内核将从线程索引指示的位置读取数组中的参数,并使用它们执行SomeFunction():

#include<iostream>
#include<cuda_runtime.h>

int SomeFunctionHost(int x, int y)
{
    return x*y;
}

__device__ int SomeFunction(int x, int y)
{
    return x*y;
}

__global__ void executionKernel(int2 * args, int * results,  int n)
{
    int blockId = blockIdx.x;
    int threadId = blockId * (blockDim.x * blockDim.y) + (threadIdx.y * blockDim.x) + threadIdx.x;
    if(threadId < n)
    {
        int2 myArgs = args[threadId];
        results[threadId] = SomeFunction(myArgs.x, myArgs.y);
    }
} 

int main ()
{  
    //Create execution parameters
    int Nx_max=1000000;
    int *Ny;
    int Nx, i;

    Ny=(int*)malloc(Nx_max*sizeof(int));
    UpdateArray(Ny, &Nx);

    int count = 0;
    for(i=0; i<Nx; i++) {
        count += Ny[i];
    }

    int2 * hParams, * dParams;
    int * hResults, * dResults;

    hParams = (int2*)malloc(count*sizeof(int2));
    hResults = (int* )malloc(count*sizeof(int));

    cudaMalloc( (void **) &dParams, count*sizeof(int2));
    cudaMalloc( (void **) &dResults, count*sizeof(int));

    cudaMemset( dResults, 0 , count * sizeof(int));

    int index = 0;
    for(i=0; i<Nx; i++)
    {
        for(int j=0; j<Ny[i];j++)
        {
            hParams[index].x = i;
            hParams[index].y = j;
            index++;
        }
    }   

    //Copy execution parameters to the device
    cudaMemcpy(dParams, hParams, count * sizeof(int2), cudaMemcpyHostToDevice);

    //Define the grid configuration
    dim3 blockDim(32,32,1);
    int gridLength = count/(blockDim.x*blockDim.y) + 1;
    dim3 gridDim(gridLength, 1, 1);

    //Run kernel
    executionKernel<<<gridDim, blockDim>>>(dParams, dResults, count);

    //Copy the results back to the host
    cudaMemcpy(hResults, dResults, count * sizeof(int), cudaMemcpyDeviceToHost);

    //TEST
    for(int i=0;i<count;i++)
    {
        if(SomeFunctionHost(hParams[i].x, hParams[i].y) != hResults[i])
        {
            std::cout << "WRONG RESULT !" << std::endl;
        }
    }

    std::cout << "DONE!" << std::endl;

    free(hParams);
    free(hResults);
    free(dParams);
    free(dResults);
    return 0;    
}

这样可以确保网格之间的工作分布均匀。我强烈建议您尝试不同的网格配置。也许尝试并行​​化参数生成阶段,看看它是否给你带来任何好处。玩得开心!