Как реализовать RAII типа API CUDA cudaEvent_t с помощью shared_ptr
API CUDA имеет типы, которые требуют вызовов create() и destroy(), аналогичных выделению памяти new и delete. В духе RAII, и вместо того, чтобы вызывать cudaEventCreate( &event) и cudaEventDestory( event), я написал следующую оболочку для cudaEvent_t.
Мой вопрос: это приемлемый код без каких-либо явных ошибок?
Это строит для меня, и я еще не обнаружил проблему. Но мне особенно не нравится хитрость reinterpret_cast<>, используемая для получения переменной cudaEvent_t через пользовательский Allocater и Deleter для shared_ptr.
Некоторые похожие посты:
CUDA: Распределение памяти устройства в C++
Есть ли лучший / чище / более элегантный способ malloc и бесплатно в 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()) )
{ }
};
2 ответа
Вы объединяете два независимых механизма: класс RAII для событий CUDA и управление временем жизни с помощью общего указателя. Они должны быть совершенно разными.
Другая проблема заключается в том, что неясно, что должен делать ваш checkCudaErrors.
Последняя проблема - это упомянутые талоны, которые должны были произойти, если вы неправильно укажете область действия / время жизни. Например - вы сбросили устройство до того, как была выпущена последняя ссылка на это событие. Или - вы ставите это событие в очередь, а затем добавляете точку к нему. Таким образом, вы не гарантируете безопасность с помощью общего указателя - вам придется следить за вещами так же, как если бы у вас был только идентификатор. На самом деле, это может сделать вещи еще сложнее.
Наконец, обратите внимание, что вы можете использовать API времени выполнения CUDA с оболочками modern-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() );