Cuda的嵌套并行性

时间:2015-11-09 17:42:23

标签: cuda parallel-processing gpu nested-loops

在下面的代码中,我想使用嵌套并行性来计算数组元素的10倍。我用这个简单的例子来了解Cuda中关于动态并行性的更多信息。代码的工作方式是,对于parentArray的每个元素,还有另一个内核将此元素保存在childArray(0到9)的位置。因此,对于parentArray的每个元素,我有另一个包含10个元素的数组,每个元素都等于parentArray的元素。最后,我计算所有childArrays的总和,并将结果保存在parentArray中。

因此结果应为:

parentArray的元素0,结果= 0
parentArray的元素1,结果= 10
parentArray的元素2,Result = 20,依此类推

目前,代码编译但未给出预期结果。当前代码有什么问题?

计算元素总和的函数

__device__ double summe(double *arr, int size)
{
  double result = 0.0;
  for(int i = 0; i < size; i++)
  {
    result += arr[i];
  }
  return result;
}

从childKernel调用的函数

__device__ double getElement(double arrElement)
{
  return arrElement;
}

存储结果的数组

__device__ double childArr[10];

childKernel

__global__ void childKernel(double *arr, double arrElement,int N)
{
  int cidx = blockIdx.x * blockDim.x + threadIdx.x;
  if (cidx < N)
  {
    arr[cidx] = getElement(arrElement);
  }
}

parentKernel

__global__ void parentKernel(double *parentArray, int N)
{
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N)
  {
    childKernel<<<1,10>>>(childArr,parentArray[idx],N);
    __syncthreads();
    parentArray[idx] = summe(childArr,10);

  }

}

主要内容

 int main(void)
    {

      double *host_array;
      double *device_array;

      // Number of elements in arrays
      const int N_array = 10;

      // size of array
      const size_t size_array = N_array * sizeof(double);

      // Allocate array on host
      host_array = (double *)malloc(size_array);

      // Allocate array on device
      CUDA_CALL(cudaMalloc((void **) &device_array, size_array));

      // Initialize host array
      for (int i=0; i<N_array; i++)
      {
        host_array[i] = (double)i;
      }

      // and copy it to CUDA device
      CUDA_CALL(cudaMemcpy(device_array, host_array, size_array, cudaMemcpyHostToDevice));

      // Do calculation on device:
      int block_size = 4;
      // if N = 10, then n_blocks = 3
      int n_blocks = N_array/block_size + (N_array % block_size == 0 ? 0:1);

      parentKernel<<<n_blocks, block_size>>>(device_array,N_array);

      // Retrieve result from device and store it in host array
      CUDA_CALL(cudaMemcpy(host_array, device_array, sizeof(double)*N_array, cudaMemcpyDeviceToHost));

  // Print results
  for (int i=0; i<N_array; i++)
  {
    printf("Element %d of parentArray, Result = %f\n", i, host_array[i]);
  }

  // Cleanup
  free(host_array);
  CUDA_CALL(cudaFree(device_array));

}

我得到的结果是:

0 52.000000
1 52.000000
2 52.000000
3 52.000000
4 48.000000
5 48.000000
6 48.000000
7 48.000000
8 48.000000
9 48.000000

我使用Cuda 6.5
NVCCFLAGS = -arch = sm_35 -rdc = true -G -O3 --compiler-options -Wall

/opt/cuda-6.5/bin/nvcc -V 

nvcc: NVIDIA (R) Cuda compiler driver    
Copyright (c) 2005-2014 NVIDIA Corporation    
Built on Thu_Jul_17_21:41:27_CDT_2014   
Cuda compilation tools, release 6.5, V6.5.12 

1 个答案:

答案 0 :(得分:2)

此时你正在启动10个内核(每个子内核也有10个线程),10个活动父内核线程中的每一个都有一个:

childKernel<<<1,10>>>(childArr,parentArray[idx],N);

这10个内核将以任何顺序运行,彼此完全异步。此外,这10个内核中的每一个都试图将值写入childArr中的相同的10个位置。所以这是竞争条件。此时childArr的最终结果为:

__syncthreads();

将无法预测。

避免竞争条件的一种可能方法是让每个子内核写入childArr的单独部分。

另一个问题是使用__syncthreads()而不是cudaDeviceSynchronize()作为内核中的障碍。无论是来自主机还是设备代码,内核启动都是异步的,而__syncthreads()并不能保证异步启动的先前工作完成。 cudaDeviceSynchronize()导致调用线程暂停,直到该线程启动的所有先前内核完成为止。 (见下面的注释)

通过这两项更改,您的代码可以生成您期望的输出:

$ cat t11.cu
#include <stdio.h>
#define CUDA_CALL(x) x
#define MY_M 10
#define MY_N 10

__device__ double childArr[MY_M*MY_N];

__device__ double summe(double *arr, int size)
{
  double result = 0.0;
  for(int i = 0; i < size; i++)
  {
    result += arr[i];
  }
  return result;
}

__device__ double getElement(double arrElement)
{
  return arrElement;
}

__global__ void childKernel(double *arr, double arrElement,int N)
{
  int cidx = blockIdx.x * blockDim.x + threadIdx.x;
  if (cidx < N)
  {
    arr[cidx] = getElement(arrElement);
  }
}

__global__ void parentKernel(double *parentArray, int N)
{
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  if (idx < N)
  {
    childKernel<<<1,MY_M>>>(childArr+MY_M*idx,parentArray[idx],N);
    cudaDeviceSynchronize();
    parentArray[idx] = summe(childArr+MY_M*idx,MY_M);

  }

}

int main(void)
    {

      double *host_array;
      double *device_array;

      // Number of elements in arrays
      const int N_array = MY_N;

      // size of array
      const size_t size_array = N_array * sizeof(double);

      // Allocate array on host
      host_array = (double *)malloc(size_array);

      // Allocate array on device
      CUDA_CALL(cudaMalloc((void **) &device_array, size_array));

      // Initialize host array
      for (int i=0; i<N_array; i++)
      {
        host_array[i] = (double)i;
      }

      // and copy it to CUDA device
      CUDA_CALL(cudaMemcpy(device_array, host_array, size_array, cudaMemcpyHostToDevice));

      // Do calculation on device:
      int block_size = 4;
      // if N = 10, then n_blocks = 3
      int n_blocks = N_array/block_size + (N_array % block_size == 0 ? 0:1);

      parentKernel<<<n_blocks, block_size>>>(device_array,N_array);

      // Retrieve result from device and store it in host array
      CUDA_CALL(cudaMemcpy(host_array, device_array, sizeof(double)*N_array, cudaMemcpyDeviceToHost));

  // Print results
  for (int i=0; i<N_array; i++)
  {
    printf("Element %d of parentArray, Result = %f\n", i, host_array[i]);
  }

  // Cleanup
  free(host_array);
  CUDA_CALL(cudaFree(device_array));

}


$ nvcc -arch=sm_52 -rdc=true -o t11 t11.cu -lcudadevrt
$ cuda-memcheck ./t11
========= CUDA-MEMCHECK
Element 0 of parentArray, Result = 0.000000
Element 1 of parentArray, Result = 10.000000
Element 2 of parentArray, Result = 20.000000
Element 3 of parentArray, Result = 30.000000
Element 4 of parentArray, Result = 40.000000
Element 5 of parentArray, Result = 50.000000
Element 6 of parentArray, Result = 60.000000
Element 7 of parentArray, Result = 70.000000
Element 8 of parentArray, Result = 80.000000
Element 9 of parentArray, Result = 90.000000
========= ERROR SUMMARY: 0 errors
$

请注意,通常应编译CDP代码:

  1. 计算能力为3.5或更高
  2. 使用-rdc=true开关(或等效序列,例如-dc后跟设备链接)
  3. 使用-lcudadevrt开关,以获取设备运行时库。
  4. 注意:实际上,在前一个子内核调用之后,从父线程调用的cudaDeviceSynchronize()将暂停该线程,直到所有先前从中的任何线程启动的内核完成为止。 (documentation)但是,由于块中的线程不能保证彼此锁定执行,因此其他线程中的内核在特定点上启动的内容可能并不明显。因此,正确使用可能涉及__syncthreads()(以保证其他线程中的子内核已启动),紧接着cudaDeviceSynchronize()以确保那些子内核已完成,如果这是所需行为。但是,在这种特殊情况下,给定父线程的结果在完成其他父线程子内核时不依赖,因此在这种情况下我们可以省略__syncthreads(),将其替换为cudaDeviceSynchronize()