2008-11-18 14 views
17

Zaczynam używać CUDA w tej chwili i muszę przyznać, że jestem trochę rozczarowany C API. Rozumiem powody wyboru języka C, ale jeśli język był oparty na C++, kilka aspektów byłoby o wiele prostszych, np. przydzielanie pamięci urządzenia (via cudaMalloc).CUDA: Alokacja pamięci urządzenia pakującego w C++

Mój plan polegał na zrobieniu tego samemu, używając przeciążonego operator new z miejscami docelowymi new i RAII (dwie alternatywy). Zastanawiam się, czy są jakieś zastrzeżenia, których do tej pory nie zauważyłem. Kod wydaje się być do pracy, ale wciąż zastanawiam się nad potencjalnymi wyciekami pamięci.

Korzystanie z RAII kodu byłby następujący:

CudaArray<float> device_data(SIZE); 
// Use `device_data` as if it were a raw pointer. 

Może klasa jest przesadą w tym kontekście (zwłaszcza odkąd trzeba jeszcze używać cudaMemcpy klasa tylko enkapsulacji RAII) więc inne podejście byłoby placement new:

float* device_data = new (cudaDevice) float[SIZE]; 
// Use `device_data` … 
operator delete [](device_data, cudaDevice); 

Tutaj cudaDevice działa jedynie jako znak do wywołać przeciążenie. Ponieważ jednak w zwykłym umieszczeniu new wskazuje to na umieszczenie, uważam, że składnia jest dziwnie spójna i być może nawet bardziej pożądana niż użycie klasy.

Byłbym wdzięczny za krytykę każdego rodzaju. Czy ktoś może wie, czy coś w tym kierunku jest planowane dla następnej wersji CUDA (co, jak słyszałem, poprawi jej obsługę C++, cokolwiek mają na myśli).

Więc moje pytanie jest faktycznie trojakie:

  1. Czy mój placement new przeciążenie semantycznie poprawne? Czy przecieka pamięć?
  2. Czy ktoś ma informacje o przyszłych projektach CUDA, które mają ten ogólny kierunek (spójrzmy prawdzie w oczy: interfejsy C w C++ s * ck)?
  3. Jak mogę dalej to robić w spójny sposób (istnieją inne API do rozważenia, np. Nie tylko pamięć urządzenia, ale także stały magazyn pamięci i pamięć tekstur)?

// Singleton tag for CUDA device memory placement. 
struct CudaDevice { 
    static CudaDevice const& get() { return instance; } 
private: 
    static CudaDevice const instance; 
    CudaDevice() { } 
    CudaDevice(CudaDevice const&); 
    CudaDevice& operator =(CudaDevice const&); 
} const& cudaDevice = CudaDevice::get(); 

CudaDevice const CudaDevice::instance; 

inline void* operator new [](std::size_t nbytes, CudaDevice const&) { 
    void* ret; 
    cudaMalloc(&ret, nbytes); 
    return ret; 
} 

inline void operator delete [](void* p, CudaDevice const&) throw() { 
    cudaFree(p); 
} 

template <typename T> 
class CudaArray { 
public: 
    explicit 
    CudaArray(std::size_t size) : size(size), data(new (cudaDevice) T[size]) { } 

    operator T*() { return data; } 

    ~CudaArray() { 
     operator delete [](data, cudaDevice); 
    } 

private: 
    std::size_t const size; 
    T* const data; 

    CudaArray(CudaArray const&); 
    CudaArray& operator =(CudaArray const&); 
}; 

O Singleton zatrudniony tutaj: Tak, jestem świadoma swoich wad. Jednak nie są one istotne w tym kontekście. Jedyne, czego potrzebowałem, to mały tag, którego nie można było skopiować. Cała reszta (tj. Wielowątkowość, czas inicjalizacji) nie ma zastosowania.

+1

Twoja implementacja singletonu jest w najlepszym wypadku niebezpieczna. Zobacz wiele innych dyskusji na temat tworzenia singleton w C++. –

+0

Tak, masz rację. Zobacz jednak moje nowe wyjaśnienie poniżej kodu. –

Odpowiedz

5

Chciałbym pójść z nowym podejściem rozmieszczenia. Następnie zdefiniowałbym klasę zgodną z interfejsem std :: allocator <>. Teoretycznie możesz przekazać tę klasę jako parametr szablonu do std :: vector <> i std :: map <> i tak dalej.

Uwaga! Słyszałem, że robienie takich rzeczy jest pełne trudności, ale przynajmniej w ten sposób nauczysz się dużo więcej o STL. I nie musisz ponownie wymyślać swoich pojemników i algorytmów.

+0

Nie myślałem o alokatorze. Robiłem to wcześniej, więc nie powinno to być zbyt trudne. –

2

Istnieje kilka projektów, które próbują czegoś podobnego, na przykład CUDPP.

W międzyczasie jednak zaimplementowałem swój własny przydział i działa on dobrze i był prosty (> 95% kodu standardowego).

+0

Łącze stdcuda jest martwe. – einpoklum

+0

@einpoklum Dzięki. Oznacza to, że 10-letnia odpowiedź byłaby w pewnym momencie nieaktualna. Usunąłem link. –

7

W międzyczasie nastąpił dalszy rozwój (nie tyle pod względem API CUDA, ale przynajmniej pod względem projektów próbujących podobnego do STL podejścia do zarządzania danymi CUDA).

Przede wszystkim jest to projekt od badań NVIDIA: thrust

1

Czy ktoś ma informacje na temat przyszłych zmian CUDA, które wchodzą w tym ogólnym kierunku (nie oszukujmy się: interfejsy C w C++ S * ck)?

Tak, zrobiłem coś takiego:

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

NVIDIA Runtime API dla CUDA jest przeznaczony do stosowania zarówno w C i C++ kod. Jako taki używa API w stylu C, dolnego wspólnego mianownika (z kilkoma godnymi uwagi wyjątkami przeciążenia funkcji szablonowych).

Ta biblioteka wrapperów wokół Runtime API ma umożliwić nam korzystanie z wielu funkcji C++ (w tym niektórych C++ 11) do korzystania z interfejsu API środowiska wykonawczego - ale bez zmniejszania ekspresji lub zwiększania poziomu abstrakcji (jak np. w bibliotece Thrust). Używając cuda-api-wrapperów, wciąż masz swoje urządzenia, strumienie, zdarzenia itd. - ale będą wygodniejsze w pracy w bardziej C++ - idiomatyczny sposób.