CUDA:用C++封装设备内存分配

17

我目前开始使用CUDA,不得不承认我对C API有点失望。我理解选择C的原因,但如果语言基于C++,例如通过cudaMalloc分配设备内存等方面将会更加简单。

我的计划是自己完成这个任务,使用重载的operator new和RAII(两种替代方案)进行放置new,我想知道是否存在任何我尚未注意到的注意事项。代码似乎可以工作,但我仍在担心可能存在的内存泄漏。

RAII代码的使用如下:

CudaArray<float> device_data(SIZE);
// Use `device_data` as if it were a raw pointer.

也许在这种情况下使用类有点过度(特别是因为你仍然需要使用 cudaMemcpy,而类只是封装了 RAII),所以另一种方法是使用就地 new
float* device_data = new (cudaDevice) float[SIZE];
// Use `device_data` …
operator delete [](device_data, cudaDevice);

在这里,cudaDevice 只是一个标签,用于触发重载。然而,在正常的放置 new 中,这将表示放置,我认为这种语法奇怪但一致性可能甚至优于使用类。

我希望能得到各方面的批评。是否有人知道 CUDA 的下一个版本计划中是否会朝着这个方向发展(据我所知,它将改善其 C++ 支持,无论他们指的是什么)。

因此,我的问题实际上有三个:

  1. 我的放置 new 重载语义上正确吗?是否会泄漏内存?
  2. 是否有关于未来 CUDA 开发朝着这个方向发展的信息(让我们面对现实:C 接口在 C++ 中很糟糕)?
  3. 如何以一致的方式进一步处理此问题(还有其他 API 要考虑,例如不仅有设备内存还有常量内存和纹理内存)?

// Singleton tag for CUDA device memory placement.
struct CudaDevice {
    static CudaDevice const& get() { return instance; }
private:
    static CudaDevice const instance;
    CudaDevice() { }
    CudaDevice(CudaDevice const&);
    CudaDevice& operator =(CudaDevice const&);
} const& cudaDevice = CudaDevice::get();

CudaDevice const CudaDevice::instance;

inline void* operator new [](std::size_t nbytes, CudaDevice const&) {
    void* ret;
    cudaMalloc(&ret, nbytes);
    return ret;
}

inline void operator delete [](void* p, CudaDevice const&) throw() {
    cudaFree(p);
}

template <typename T>
class CudaArray {
public:
    explicit
    CudaArray(std::size_t size) : size(size), data(new (cudaDevice) T[size]) { }

    operator T* () { return data; }

    ~CudaArray() {
        operator delete [](data, cudaDevice);
    }

private:
    std::size_t const size;
    T* const data;

    CudaArray(CudaArray const&);
    CudaArray& operator =(CudaArray const&);
};

关于此处使用的单例模式:是的,我知道它的缺点。但是,在这种情况下这些缺点并不相关。我所需要的只是一个无法复制的小型类型标记。其他方面(例如多线程考虑和初始化时间)并不适用。


1
你的单例模式实现至多是危险的。请查看关于如何在C++中创建单例模式的许多其他讨论。 - Martin York
是的,你说得对。不过,请看一下我在代码下面的新澄清。 - Konrad Rudolph
4个回答

7
与此同时,还有一些进展(在CUDA API方面不是很多,但至少在试图采用类STL方式管理CUDA数据的项目方面有所发展)。
最值得注意的是来自NVIDIA研究的一个项目:thrust

5
我会选择使用放置new方法。然后我会定义一个符合std :: allocator<>接口的类。理论上,您可以将此类作为模板参数传递给std :: vector<>、std :: map<>等等。
请注意,我听说这样做充满了困难,但至少您将通过这种方式学到更多关于STL的知识。而且您不需要重新发明您的容器和算法。

没想过使用分配器。我以前做过这个,所以应该不太难。 - Konrad Rudolph

2

有一些类似的项目,比如CUDPP

同时,我已经实现了自己的分配器,它运行良好且非常简单(占总代码量的>95%)。


stdcuda链接已失效。 - einpoklum
@einpoklum 谢谢。很明显,一个10年前的答案在某个时候会过时。我已经删除了链接。 - Konrad Rudolph

2
有人对 CUDA 未来的发展方向有信息吗?(面对现实吧:C++ 中的 C 接口很糟糕)。是的,我已经做过类似的事情了。

https://github.com/eyalroz/cuda-api-wrappers/

nVIDIA的CUDA运行时API旨在用于C和C++代码。因此,它使用C风格的API作为较低的公共分母(除了一些显著的函数模板重载)。这个围绕着运行时API的包装库旨在允许我们采用许多C++特性(包括一些C++11)来使用运行时API,但不会降低表达能力或增加抽象级别(例如,Thrust库中的情况)。使用cuda-api-wrappers,您仍然可以使用设备、流、事件等等,但它们将更方便以更符合C++习惯的方式进行操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接