如何使用 shared_ptr 实现 CUDA API 类型 cudaEvent_t 的 RAII

How to implement RAII of CUDA API type cudaEvent_t using shared_ptr

CUDA API 具有需要调用 create() 和 destroy() 的类型,类似于内存分配新建和删除。本着 RAII 的精神,我不必调用 cudaEventCreate( &event) 和 cudaEventDestory( event ),而是为 cudaEvent_t.

编写了以下包装器

我的问题:这个代码是否可以接受,没有任何明显的错误?

它是为我构建的,我还没有发现问题。但我特别不喜欢 reinterpret_cast<> 用于通过 shared_ptr.

的自定义分配器和删除器获取 cudaEvent_t 变量的技巧

一些相关帖子:

CUDA: Wrapping device memory allocation in C++

class CudaEvent {
private:
    struct Deleter {
        void operator()(cudaEvent_t * ptr) const {
            checkCudaErrors( cudaEventDestroy( reinterpret_cast<cudaEvent_t>(ptr) ));
        }
    };

    shared_ptr<cudaEvent_t> Allocate( ){
        cudaEvent_t event;
        checkCudaErrors( cudaEventCreate( &event ) );
        shared_ptr<cudaEvent_t> p( reinterpret_cast<cudaEvent_t*>(event), Deleter() );
        return p;
    }

    shared_ptr<cudaEvent_t> ps;

public:
    cudaEvent_t event;

    CudaEvent(  )
    : ps( Allocate( ) ),
      event( *(ps.get()) )
    {   }
};

您将两个独立的机制混为一谈:用于 CUDA 事件的 RAII class 和使用共享指针的生命周期管理。这些应该是完全分开的。

另一个问题是不清楚你的 "checkCudaErrors" 应该做什么。

最后一个问题是提到的 one talonmies,如果 scope/lifetime 错误,就会发生这种情况。例如 - 您在发布对此事件的最后一次引用之前重置了设备。或者 - 您将此事件排入流中,然后将点拖放到它。因此,使用共享指针并不能真正保证安全——您必须像只有 id 一样跟踪事物。事实上,这可能会使事情变得更加困难。

最后,请注意,您可以将 CUDA 运行时 API 与 modern-C++ 包装器一起使用,特别是使用 RAII 而不是 createXYZ() 和 destroyXYZ():

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

具体可以看看:

应有的披露:我是这个库的作者。

你假设 cudaEvent_t 可以与 (void *) 相互转换:

shared_ptr<cudaEvent_t> Allocate( ){
    cudaEvent_t event;
    checkCudaErrors( cudaEventCreate( &event ) );
    shared_ptr<cudaEvent_t> p( reinterpret_cast<cudaEvent_t*>(event), Deleter() );
    return p;
}

“正确的”强制转换会更糟,因为它会像 int *Allocate() { int i; return &i; }.

一样创建悬空引用

正确的 C++ 模式是将 cudaEvent_t 与 class 的生命周期相关联(并实现 move/copy 构造函数)或者直接使用指向 cudaEvent_t.

std::shared_ptr<cudaEvent_t> event (
                    new cudaEvent_t,
                    [](cudaEvent_t *e){ cudaEventDestroy(*e); });
cudaEventCreate( event.get() );