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



CUDA API 具有需要 create() 和 destroy() 调用的类型,类似于内存分配 new 和 delete。 本着RAII的精神,我不必调用cudaEventCreate(&event)和cudaEventDestory(event),而是为cudaEvent_t编写了以下包装器。

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

它为我构建,我还没有发现问题。 但我特别不喜欢用于通过自定义 Allocater 和 Delete shared_ptr r 获取cudaEvent_t变量的reinterpret_cast<>技巧。

一些相关帖子:

CUDA:在C++中包装设备内存分配

有没有更好/更干净/更优雅的方式来在 cuda 中免费?

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 类和使用共享指针的生命周期管理。这些应该是完全分开的。

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

最后一个问题是提到的一个问题,如果你把范围/寿命弄错了,会发生什么。例如 - 在释放对此事件的最后一个引用之前,你已重置设备。或者 - 您将此事件排在流上,然后将该点放到它。因此,使用共享指针并不能真正保证安全性 - 您必须像只有 ID 一样跟踪事情。事实上,这可能会使事情变得更加困难。

最后,请注意,您可以将 CUDA 运行时 API 与现代C++包装器一起使用,具体来说,使用 RAII 而不是 createXYZ() 和 destroyXYZ():

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

具体来说,你可以看看:

  • cuda::event_t类 Doxygen 文档。
  • 使用和管理事件的示例程序。

适当披露:我是这个库的作者。

你假设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与类的生存期相关联(并实现移动/复制构造函数),或者直接使用指向cudaEvent_t的共享指针。

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

最新更新