【问题标题】:Use data allocated dynamically in CUDA kernel on host使用主机上 CUDA 内核中动态分配的数据
【发布时间】:2017-02-02 10:55:14
【问题描述】:

我正在尝试在管理一些内存的设备上构建一个容器类。 该内存是在内核中的对象构造期间动态分配和填充的。 根据可以在内核中使用简单的 new[] 完成的文档(在 Visual Studio 2012 中使用具有计算能力 5.0 的 CUDA 8.0)。 之后我想在主机代码中访问容器内的数据(例如,用于测试所有值是否正确)。

DeviceContainer 类的最小版本如下所示:

class DeviceContainer 
{
public:
   __device__ DeviceContainer(unsigned int size);
   __host__ __device__ ~DeviceContainer();

   __host__ __device__ DeviceContainer(const DeviceContainer & other);
   __host__ __device__ DeviceContainer & operator=(const DeviceContainer & other);

   __host__ __device__ unsigned int getSize() const { return m_sizeData; }
   __device__ int * getDataDevice() const { return mp_dev_data; }
   __host__ int* getDataHost() const;

private:
   int * mp_dev_data;
   unsigned int m_sizeData;
};


__device__ DeviceContainer::DeviceContainer(unsigned int size) :
      m_sizeData(size), mp_dev_data(nullptr) 
{
   mp_dev_data = new int[m_sizeData];

   for(unsigned int i = 0; i < m_sizeData; ++i) {
      mp_dev_data[i] = i;
   }
}


__host__ __device__ DeviceContainer::DeviceContainer(const DeviceContainer & other) : 
  m_sizeData(other.m_sizeData)
{
#ifndef __CUDA_ARCH__
   cudaSafeCall( cudaMalloc((void**)&mp_dev_data, m_sizeData * sizeof(int)) );
   cudaSafeCall( cudaMemcpy(mp_dev_data, other.mp_dev_data, m_sizeData * sizeof(int), cudaMemcpyDeviceToDevice) );
#else
   mp_dev_data = new int[m_sizeData];
   memcpy(mp_dev_data, other.mp_dev_data, m_sizeData * sizeof(int));
#endif
}


__host__ __device__ DeviceContainer::~DeviceContainer()
{
#ifndef __CUDA_ARCH__
   cudaSafeCall( cudaFree(mp_dev_data) );
#else
   delete[] mp_dev_data;
#endif
   mp_dev_data = nullptr;
}


__host__ __device__ DeviceContainer & DeviceContainer::operator=(const DeviceContainer & other)
{
   m_sizeData = other.m_sizeData;

 #ifndef __CUDA_ARCH__
   cudaSafeCall( cudaMalloc((void**)&mp_dev_data, m_sizeData * sizeof(int)) );
   cudaSafeCall( cudaMemcpy(mp_dev_data, other.mp_dev_data, m_sizeData * sizeof(int), cudaMemcpyDeviceToDevice) );
#else
   mp_dev_data = new int[m_sizeData];
   memcpy(mp_dev_data, other.mp_dev_data, m_sizeData * sizeof(int));
#endif

   return *this;
}


__host__ int* DeviceContainer::getDataHost() const
{
   int * pDataHost = new int[m_sizeData];
   cudaSafeCall( cudaMemcpy(pDataHost, mp_dev_data, m_sizeData * sizeof(int), cudaMemcpyDeviceToHost) );
   return pDataHost;
}

它只管理数组mp_dev_data。 数组是在构造过程中创建并填充连续值的,这应该只能在设备上实现。 (请注意,实际上容器的大小可能彼此不同。)

我认为我需要提供一个复制构造函数和一个赋值运算符,因为我不知道任何其他方法可以在内核中填充数组。 (见下面的问题 3。) 由于主机上也可能发生复制和删除,__CUDA_ARCH__ 用于确定我们正在编译的执行路径。在主机上使用cudaMemcpycudaFree,在设备上我们可以使用memcpydelete[]

对象创建的内核相当简单:

__global__ void createContainer(DeviceContainer * pContainer, unsigned int numContainer, unsigned int containerSize)
{
   unsigned int offset = blockIdx.x * blockDim.x + threadIdx.x;

   if(offset < numContainer)
   {
      pContainer[offset] = DeviceContainer(containerSize);
   }
}

范围内的一维网格中的每个线程都会创建一个容器对象。

然后主函数为设备和主机上的容器(本例中为 90000)分配数组,调用内核并尝试使用对象:

void main()
{
   const unsigned int numContainer = 90000;
   const unsigned int containerSize = 5;

   DeviceContainer * pDevContainer;
   cudaSafeCall( cudaMalloc((void**)&pDevContainer, numContainer * sizeof(DeviceContainer)) );

   dim3 blockSize(1024, 1, 1);
   dim3 gridSize((numContainer + blockSize.x - 1)/blockSize.x , 1, 1);

   createContainer<<<gridSize, blockSize>>>(pDevContainer, numContainer, containerSize);
   cudaCheckError();

   DeviceContainer * pHostContainer = (DeviceContainer *)malloc(numContainer * sizeof(DeviceContainer)); 
   cudaSafeCall( cudaMemcpy(pHostContainer, pDevContainer, numContainer * sizeof(DeviceContainer), cudaMemcpyDeviceToHost) );

   for(unsigned int i = 0; i < numContainer; ++i)
   {
      const DeviceContainer & dc = pHostContainer[i];

      int * pData = dc.getDataHost();
      for(unsigned int j = 0; j < dc.getSize(); ++j)
      {
         std::cout << pData[j];
      }
      std::cout << std::endl;
      delete[] pData;
   }

   free(pHostContainer);
   cudaSafeCall( cudaFree(pDevContainer) );
}

我必须使用malloc 在主机上创建数组,因为我不想为DeviceContainer 使用默认构造函数。 我尝试通过getDataHost() 访问容器内的数据,内部只调用cudaMemcpy

cudaSafeCallcudaCheckError 是简单的宏,它们评估函数 oder 返回的 cudaError 主动轮询最后一个错误。为了完整起见:

#define cudaSafeCall(error) __cudaSafeCall(error, __FILE__, __LINE__)
#define cudaCheckError()    __cudaCheckError(__FILE__, __LINE__)

inline void __cudaSafeCall(cudaError error, const char *file, const int line)
{
   if (error != cudaSuccess)
   {
      std::cerr << "cudaSafeCall() returned:" << std::endl;
      std::cerr << "\tFile: " << file << ",\nLine: " << line << " - CudaError " << error << ":" << std::endl;
      std::cerr << "\t" << cudaGetErrorString(error) << std::endl;

      system("PAUSE");
      exit( -1 );
   }
}


inline void __cudaCheckError(const char *file, const int line)
{
   cudaError error = cudaDeviceSynchronize();
   if (error != cudaSuccess)
   {
      std::cerr << "cudaCheckError() returned:" << std::endl;
      std::cerr << "\tFile: " << file << ",\tLine: " << line << " - CudaError " << error << ":" << std::endl;
      std::cerr << "\t" << cudaGetErrorString(error) << std::endl;

      system("PAUSE");
      exit( -1 );
   }
}

我对这段代码有 3 个问题:

  1. 如果按照此处所示执行,我会收到内核的“未指定启动失败”。 Nsight 调试器在mp_dev_data = new int[m_sizeData]; 行(在构造函数或赋值运算符中)阻止我并报告全局内存上的几个访问冲突。违规次数似乎在 4 到 11 之间是随机的,它们发生在不连续的线程中,但总是靠近网格的上端(块 85 和 86)。

  2. 1234563分配错误,内存已被另一个对象删除。)
  3. 尽管我想知道如何通过适当的内存管理正确实现DeviceContainer,但在我的情况下,使其不可复制和不可分配也足够了。但是,我不知道如何正确填充内核中的容器数组。也许像

    DeviceContainer dc(5); memcpy(&pContainer[offset], &dc, sizeof(DeviceContainer));

    这会导致在析构函数中删除mp_dev_data 时出现问题。我需要手动管理感觉很脏的内存删除。

我也尝试在内核代码中使用mallocfree,而不是newdelete,但结果是一样的。

很抱歉,我无法以更短的方式提出我的问题。

TL;DR:如何实现一个在内核中动态分配内存并且也可以在主机代码中使用的类?如何使用无法复制或分配的对象初始化内核中的数组?

感谢任何帮助。谢谢。

【问题讨论】:

    标签: c++ cuda dynamic-memory-allocation memcpy


    【解决方案1】:

    显然答案是:我想做的事或多或少是不可能的。 在内核中使用newmalloc 分配的内存不会放在全局内存中,而是放在主机无法访问的特殊堆内存中。

    访问主机上所有内存的唯一选择是首先在全局内存中分配一个数组,该数组大到足以容纳堆上的所有元素,然后编写一个内核,将所有元素从堆复制到全局内存。

    访问冲突是由有限的堆大小引起的(可以通过cudaDeviceSetLimit(cudaLimitMallocHeapSize, size_t size)更改。

    【讨论】:

      猜你喜欢
      • 2013-09-17
      • 2011-10-19
      • 2014-06-10
      • 2012-04-06
      • 1970-01-01
      • 2012-04-13
      • 1970-01-01
      • 1970-01-01
      • 2010-09-21
      相关资源
      最近更新 更多