在cuda中有更好/更干净/更优雅的malloc和free方式吗?



我正在尝试cudaMalloc一堆设备指针,并优雅地退出,如果任何malloc不起作用。我有功能正常的代码——但是臃肿,因为我必须cudaffree之前malloc的所有代码,如果其中一个失败了。所以现在我想知道是否有一个更简洁的方法来完成它。显然,我不能释放没有malloc的东西——这肯定会导致问题。

下面是我想让它更优雅的代码片段。

    //define device pointers
    float d_norm, *d_dut, *d_stdt, *d_gamma, *d_zeta;
    //allocate space on the device for the vectors and answer
    if (cudaMalloc(&d_norm, sizeof(float)*vSize) != cudaSuccess) {
            std::cout << "failed malloc";
            return;
    };
    if (cudaMalloc(&d_data, sizeof(float)*vSize) != cudaSuccess) {
            std::cout << "failed malloc";
            cudaFree(d_norm);
            return;
    };
    if (cudaMalloc(&d_stdt, sizeof(float)*wSize) != cudaSuccess) {
            std::cout << "failed malloc";
            cudaFree(d_norm);
            cudaFree(d_data);
            return;
    };
    if (cudaMalloc(&d_gamma, sizeof(float)*vSize) != cudaSuccess) {
            std::cout << "failed malloc";
            cudaFree(d_norm);
            cudaFree(d_dut);
            cudaFree(d_stdt);
            return;
    };
    if (cudaMalloc(&d_zeta, sizeof(float)*w) != cudaSuccess) {
            std::cout << "failed malloc";
            cudaFree(d_norm);
            cudaFree(d_dut);
            cudaFree(d_stdt);
            cudaFree(d_gamma);
            return;
    };

这是一个简短的版本,但你可以看到它是如何不断构建的。实际上,我尝试malloc大约15个数组。它开始变得丑陋-但它工作正确。

想法吗?

一些可能性:

  1. cudaDeviceReset()将释放所有设备分配,而不必运行指针列表。

  2. 如果您打算退出(应用程序),所有设备分配将在应用程序终止时自动释放。cuda运行时检测与应用程序的设备上下文相关的进程的终止,并在该点清除该上下文。所以如果你要退出,不执行任何cudaFree()操作应该是安全的。

  • 你可以用自定义的deleter将它们封装到unique_ptr中。(c++ 11)

  • 或者在成功分配和释放vector中的所有指针时只添加一个vector

关于unique_ptr的示例:

#include <iostream>
#include <memory>
using namespace std;
void nativeFree(float* p);
float* nativeAlloc(float value);
class NativePointerDeleter{
public:
   void operator()(float* p)const{nativeFree(p);}
};

int main(){
   using pointer_type = unique_ptr<float,decltype(&nativeFree)>;
   using pointer_type_2 = unique_ptr<float,NativePointerDeleter>;
   pointer_type ptr(nativeAlloc(1),nativeFree);
   if(!ptr)return 0;
   pointer_type_2 ptr2(nativeAlloc(2));//no need to provide deleter
   if(!ptr2)return 0;
   pointer_type ptr3(nullptr,nativeFree);//simulate a fail alloc
   if(!ptr3)return 0;
   /*Do Some Work*/
   //now one can return without care about all the pointers
   return 0;
}
void nativeFree(float* p){
   cout << "release " << *p << 'n';
   delete p;
}
float* nativeAlloc(float value){
   return new float(value);
}

最初将nullptr存储在所有指针中。free对空指针无效

int* p1 = nullptr;
int* p2 = nullptr;
int* p3 = nullptr;
if (!(p1 = allocate()))
  goto EXIT_BLOCK;
if (!(p2 = allocate()))
  goto EXIT_BLOCK;
if (!(p3 = allocate()))
  goto EXIT_BLOCK;
EXIT_BLOCK:
free(p3); free(p2); free(p1);

问题标记为c++,所以这里有一个c++解决方案

一般做法是在构造函数中获取资源,在析构函数中释放资源。其思想是,在任何情况下,资源都保证通过调用析构函数来释放。一个简洁的副作用是,析构函数在作用域的最后被自动调用,所以当资源不再使用时,你不需要为释放它做任何事情。看到RAII

在资源的角色中,可能有各种内存类型,文件句柄,套接字等。CUDA设备内存也不例外。

我也不鼓励你编写自己拥有资源的类,并建议你使用库。thrust::device_vector可能是使用最广泛的设备内存容器。推力库是CUDA工具包的一部分。

是。如果你使用(我的)CUDA modern - c++ API包装库,你可以只使用唯一的指针,当它们的生命周期结束时将释放。您的代码将变成如下内容:

auto current_device = cuda::device::current::get();
auto d_dut   = cuda::memory::device::make_unique<float[]>(current_device, vSize);
auto d_stdt  = cuda::memory::device::make_unique<float[]>(current_device, vSize);
auto d_gamma = cuda::memory::device::make_unique<float[]>(current_device, vSize);
auto d_zeta  = cuda::memory::device::make_unique<float[]>(current_device, vSize);

请注意,您可以只分配一次指针,并将其他指针放置在适当的偏移位置。

最新更新