CUDA扩展std::vector以管理主机和设备数据 我了解到STD::vector是C++中的原始数组的一个很好的包装,所以我开始使用它来管理我的CUDA应用程序中的主机数据[1 ]。由于必须手动分配和复制东西,使得代码更加复杂,可读性较差,因此我考虑扩展std::vector。因为我不是很有经验,我想知道你对它的看法。特别是天气正确的时候(例如:STD的析构函数::vector被隐式调用,对吗?),如果你认为它是个好主意。
我写了一个小例子来说明这一点CUDA扩展std::vector以管理主机和设备数据 我了解到STD::vector是C++中的原始数组的一个很好的包装,所以我开始使用它来管理我的CUDA应用程序中的主机数据[1 ]。由于必须手动分配和复制东西,使得代码更加复杂,可读性较差,因此我考虑扩展std::vector。因为我不是很有经验,我想知道你对它的看法。特别是天气正确的时候(例如:STD的析构函数::vector被隐式调用,对吗?),如果你认为它是个好主意。,c++,stl,cuda,software-design,C++,Stl,Cuda,Software Design,我写了一个小例子来说明这一点 #include <vector> #include <cuda.h> #include <cstdio> void checkCUDAError(const char *msg) { cudaError_t err = cudaGetLastError(); if( cudaSuccess != err) { fprintf(stderr, "Cuda error: %s: %s.\n", m
#include <vector>
#include <cuda.h>
#include <cstdio>
void checkCUDAError(const char *msg)
{
cudaError_t err = cudaGetLastError();
if( cudaSuccess != err) {
fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString(err));
exit(EXIT_FAILURE);
}
}
// Wrapper around CUDA memory
template<class T>
class UniversalVector: public std::vector<T>
{
T* devicePtr_;
bool allocated;
public:
// Constructor
UniversalVector(unsigned int length)
:std::vector<T>(length),
allocated(false)
{}
// Destructor
~UniversalVector()
{
if(allocated)
cudaFree(devicePtr_);
}
cudaError_t allocateDevice()
{
if(allocated) free(devicePtr_);
cudaError_t err =
cudaMalloc((void**)&devicePtr_, sizeof(T) * this->size());
allocated = true;
return err;
}
cudaError_t loadToDevice()
{
return cudaMemcpy(devicePtr_, &(*this)[0], sizeof(T) * this->size(),
cudaMemcpyHostToDevice);
}
cudaError_t loadFromDevice()
{
return cudaMemcpy(&(*this)[0], devicePtr_, sizeof(T) * this->size(),
cudaMemcpyDeviceToHost);
}
// Accessors
inline T* devicePtr() {
return devicePtr_;
}
};
__global__ void kernel(int* a)
{
int i = threadIdx.x;
printf("%i\n", a[i]);
}
int main()
{
UniversalVector<int> vec(3);
vec.at(0) = 1;
vec.at(1) = 2;
vec.at(2) = 3;
vec.allocateDevice();
vec.loadToDevice();
kernel<<<1, 3>>>(vec.devicePtr());
checkCUDAError("Error when doing something");
return 0;
}
#包括
#包括
#包括
无效检查CUDAERROR(常量字符*msg)
{
cudaError_t err=cudaGetLastError();
如果(cudaSuccess!=错误){
fprintf(标准,“Cuda错误:%s:%s.\n”,消息,cudaGetErrorString(err));
退出(退出失败);
}
}
//CUDA内存的包装器
样板
类UniversalVector:public std::vector
{
T*设备接口;
布尔分配;
公众:
//建造师
UniversalVector(无符号整数长度)
:std::向量(长度),
已分配(错误)
{}
//析构函数
~UniversalVector()
{
如果(已分配)
cudaFree(devicePtr_);
}
cudaError\u t allocateDevice()
{
如果(已分配)空闲(devicePtr_);
cudaError\u t err=
cudaMalloc((void**)和devicePtr_,sizeof(T)*此->大小();
分配=真;
返回错误;
}
cudaError\u t loadToDevice()
{
返回cudaMemcpy(devicePtr_,&(*this)[0],sizeof(T)*this->size(),
cudamemcpyhostodevice);
}
cudaError\u t loadFromDevice()
{
返回cudaMemcpy(&(*this)[0],devicePtr,sizeof(T)*this->size(),
cudaMemcpyDeviceToHost);
}
//访问者
内联T*devicePtr(){
返回装置;
}
};
__全局无效内核(int*a)
{
int i=threadIdx.x;
printf(“%i\n”,a[i]);
}
int main()
{
UniversalVector vec(3);
向量(0)=1;
(1)处的向量=2;
(2)处的向量=3;
vec.allocateDevice();
vec.loadToDevice();
内核(vec.devicePtr());
检查CUDAERROR(“做某事时出错”);
返回0;
}
[1] 在CUDA中,主机内存和设备内存是有区别的,其中主机内存是GPU可访问的内存,设备内存是GPU上的内存,程序员必须将内存从主机移到GPU再移回来。您可能想看看。它为CUDA代码提供了一些STL容器。您可能想看看。它为CUDA代码提供了一些STL容器。您最好将
allocateDevice
和loadToDevice
等函数作为自由函数,而不是从std::vector
继承的类成员。它可以为您节省大量将其他库/类与您的资料集成在一起的时间。总体看起来不错。您最好将allocateDevice
和loadToDevice
等函数作为自由函数,而不是从std::vector
继承的类成员。它可以为您节省大量将其他库/类与您的资料集成在一起的时间。总体上看起来不错。我看到的最大问题是,这并不能真正帮助管理GPU方面的事情,而且它在处理过程中混淆了许多非常重要的信息
虽然container类包含有关是否已分配设备指针的信息,但无法知道主机容器的内容是否已复制到它所持有的GPU内存,或者GPU内存是否已复制回设备。因此,每次希望在主机或设备代码中使用容器时,都必须调用loadToDevice()
和loadFromDevice()
方法。这可能意味着至少在某些时候不必要的PCI-e内存传输。由于您选择只包装同步CUDA内存复制例程,因此每次执行此操作时都会出现主机阻塞
最终,我看不到这个想法比一套精心设计的助手例程有多大的净收益,这些例程将CUDA API中最丑陋的部分抽象出来,并在标准STL类型上运行。我看到的最大问题是,这实际上对管理GPU方面没有太大帮助,在这个过程中,它混淆了许多非常重要的信息 虽然container类包含有关是否已分配设备指针的信息,但无法知道主机容器的内容是否已复制到它所持有的GPU内存,或者GPU内存是否已复制回设备。因此,每次希望在主机或设备代码中使用容器时,都必须调用
loadToDevice()
和loadFromDevice()
方法。这可能意味着至少在某些时候不必要的PCI-e内存传输。由于您选择只包装同步CUDA内存复制例程,因此每次执行此操作时都会出现主机阻塞
最终,我看不到与一组精心设计的助手例程相比,这个想法有多大的净收益,这些例程将CUDA API中最丑陋的部分抽象出来,并在标准STL类型上运行。我将稍微扩展David Rodríguez-dribeas的评论: 为什么你应该更喜欢组合而不是继承(即使它需要额外的外观工作)这个问题已经被问过并回答了多次。一个很好的答案是: 决定因素是接口:您想要底层类的所有方法还是某些方法 在您的情况下,
std::vector
修改向量大小的方法,如resize
,push_back
,pop_back
,erase
,insert
等,如果在调用loadToDevice
之间调用,可能会造成混乱