Как реализовать 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

В частности, вы можете взглянуть на:

Из-за раскрытия информации: я являюсь автором этой библиотеки.

Вы предполагаете, что 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() );
Другие вопросы по тегам