Есть ли лучший / чище / более элегантный способ malloc и бесплатно в cuda?

Я пытаюсь скопировать кучу указателей устройств и корректно завершить работу, если какой-либо из malloc не сработал. У меня есть функционирующий код - но раздутый, потому что я должен cudaFree все, что я раньше malloc'd в случае сбоя. Так что теперь мне интересно, есть ли более лаконичный метод для достижения этой цели. Очевидно, я не могу освободить то, что не было malloc'd - это определенно вызовет проблемы.

Ниже приведен фрагмент кода, который я пытаюсь сделать более элегантным.

    //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;
    };

Это укороченная версия, но вы можете видеть, как она продолжает развиваться. На самом деле я пытаюсь распределить около 15 массивов. Это начинает становиться уродливым - но это работает правильно.

Мысли?

5 ответов

Решение

Некоторые возможности:

  1. cudaDeviceReset() освободит все устройства, без необходимости проходить через список указателей.

  2. если вы намереваетесь выйти (из приложения), все распределения устройств освобождаются автоматически по завершении приложения в любом случае. Среда выполнения cuda обнаруживает завершение процесса, связанного с контекстом устройства приложения, и стирает этот контекст в этой точке. Так что, если вы просто собираетесь выйти, должно быть безопасно не выполнять никаких cudaFree() операции.

  • Вы можете обернуть их в unique_ptr с помощью специального средства удаления. (C++11)

  • Или просто добавьте к одному вектору, когда успех выделит и освободит все указатели в векторе.

пример об уникальном_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 является, вероятно, наиболее широко используемым контейнером памяти устройства. Библиотека Thrust является частью инструментария CUDA.

Да. Если вы используете (мою) библиотеку-оболочку API CUDA Modern-C++, вы можете просто использовать уникальные указатели, которые будут освобождаться по истечении срока их службы. Ваш код станет просто следующим:

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);

Обратите внимание, что вы можете просто выделить один раз и просто разместить другие указатели с соответствующим смещением.

Другие вопросы по тегам