使用在主机上的 CUDA 内核中动态分配的数据
Use data allocated dynamically in CUDA kernel on host
我正在尝试在管理一些内存的设备上构建一个容器 class。
该内存是在内核中对象构造期间动态分配和填充的。
根据可以在内核中使用简单的 new[] 完成的文档(在 Visual Studio 2012 中使用 CUDA 8.0 和计算能力 5.0)。
之后我想访问主机代码中容器内的数据(例如,用于测试所有值是否正确)。
DeviceContainer
class 的最小版本如下所示:
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__
用于确定我们正在编译的执行路径。在主机上使用 cudaMemcpy
和 cudaFree
,在设备上我们可以只使用 memcpy
和 delete[]
.
创建对象的内核相当简单:
__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
.
cudaSafeCall
和 cudaCheckError
是简单的宏,用于评估函数返回的 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 个问题:
如果按此处所示执行,我会收到内核的 "unspecified launch failure"。 Nsight 调试器在 mp_dev_data = new int[m_sizeData];
行(在构造函数或赋值运算符中)阻止了我,并报告了几个全局内存访问冲突。违规次数似乎在 4 到 11 之间是随机的,它们发生在非连续的线程中,但总是靠近网格的上端(块 85 和 86)。
如果我将 numContainer
减少到 10,内核运行平稳,但是,getDataHost()
中的 cudaMamcpy
失败并出现无效参数错误 - 即使 mp_dev_data
不为0。(怀疑是赋值错误,内存已经被别的对象删除了。)
尽管我想知道如何通过适当的内存管理正确地实现 DeviceContainer
,但就我而言,使其不可复制和不可分配也足够了.但是,我不知道如何在内核中正确填充容器数组。也许像
DeviceContainer dc(5);
memcpy(&pContainer[offset], &dc, sizeof(DeviceContainer));
这会导致在析构函数中删除 mp_dev_data
时出现问题。我需要手动管理感觉相当脏的内存删除。
我也尝试在内核代码中使用 malloc
和 free
而不是 new
和 delete
但结果是一样的。
很抱歉,我无法以更简短的方式提出我的问题。
TL;DR:如何实现 class 在内核中动态分配内存并且也可以在主机代码中使用?如何使用无法复制或分配的对象在内核中初始化数组?
感谢任何帮助。谢谢。
显然答案是:我想做的或多或少是不可能的。
在内核中用 new
或 malloc
分配的内存不放在全局内存中,而是放在主机无法访问的特殊堆内存中。
访问主机上所有内存的唯一选择是首先在全局内存中分配一个数组,该数组足够大以容纳堆上的所有元素,然后编写一个内核将堆中的所有元素复制到全局内存.
访问冲突是由有限的堆大小引起的(可以通过cudaDeviceSetLimit(cudaLimitMallocHeapSize, size_t size)
更改。
我正在尝试在管理一些内存的设备上构建一个容器 class。 该内存是在内核中对象构造期间动态分配和填充的。 根据可以在内核中使用简单的 new[] 完成的文档(在 Visual Studio 2012 中使用 CUDA 8.0 和计算能力 5.0)。 之后我想访问主机代码中容器内的数据(例如,用于测试所有值是否正确)。
DeviceContainer
class 的最小版本如下所示:
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__
用于确定我们正在编译的执行路径。在主机上使用 cudaMemcpy
和 cudaFree
,在设备上我们可以只使用 memcpy
和 delete[]
.
创建对象的内核相当简单:
__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
.
cudaSafeCall
和 cudaCheckError
是简单的宏,用于评估函数返回的 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 个问题:
如果按此处所示执行,我会收到内核的 "unspecified launch failure"。 Nsight 调试器在
mp_dev_data = new int[m_sizeData];
行(在构造函数或赋值运算符中)阻止了我,并报告了几个全局内存访问冲突。违规次数似乎在 4 到 11 之间是随机的,它们发生在非连续的线程中,但总是靠近网格的上端(块 85 和 86)。如果我将
numContainer
减少到 10,内核运行平稳,但是,getDataHost()
中的cudaMamcpy
失败并出现无效参数错误 - 即使mp_dev_data
不为0。(怀疑是赋值错误,内存已经被别的对象删除了。)尽管我想知道如何通过适当的内存管理正确地实现
DeviceContainer
,但就我而言,使其不可复制和不可分配也足够了.但是,我不知道如何在内核中正确填充容器数组。也许像DeviceContainer dc(5); memcpy(&pContainer[offset], &dc, sizeof(DeviceContainer));
这会导致在析构函数中删除
mp_dev_data
时出现问题。我需要手动管理感觉相当脏的内存删除。
我也尝试在内核代码中使用 malloc
和 free
而不是 new
和 delete
但结果是一样的。
很抱歉,我无法以更简短的方式提出我的问题。
TL;DR:如何实现 class 在内核中动态分配内存并且也可以在主机代码中使用?如何使用无法复制或分配的对象在内核中初始化数组?
感谢任何帮助。谢谢。
显然答案是:我想做的或多或少是不可能的。
在内核中用 new
或 malloc
分配的内存不放在全局内存中,而是放在主机无法访问的特殊堆内存中。
访问主机上所有内存的唯一选择是首先在全局内存中分配一个数组,该数组足够大以容纳堆上的所有元素,然后编写一个内核将堆中的所有元素复制到全局内存.
访问冲突是由有限的堆大小引起的(可以通过cudaDeviceSetLimit(cudaLimitMallocHeapSize, size_t size)
更改。