2011-05-24 30 views
1

我瞭解到std :: vector是C++中原始數組的一個很好的包裝,因此我開始使用它來管理CUDA應用程序中的主機數據[1]。由於不得不手動分配和複製東西,所以代碼更復雜,可讀性更差,所以我想擴展std :: vector。由於我不是很有經驗,我想知道你對此有何看法。特別是天氣它正確完成(例如std :: vector的析構函數隱式調用,對嗎?),如果你認爲它是一個好主意。CUDA擴展std :: vector以管理主機和設備數據

我寫了一個小例子說明本

#include <vector> 
#include <cuda.h> 

#include <cstdio> 

void checkCUDAError(const char *msg) 
{ 
    cudaError_t err = cudaGetLastError(); 
    if(cudaSuccess != err) { 
     fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString(err)); 
     exit(EXIT_FAILURE); 
    } 
} 

// Wrapper around CUDA memory 
template<class T> 
class UniversalVector: public std::vector<T> 
{ 
    T* devicePtr_; 
    bool allocated; 

public: 

    // Constructor 
    UniversalVector(unsigned int length) 
     :std::vector<T>(length), 
     allocated(false) 
    {} 

    // Destructor 
    ~UniversalVector() 
    { 
     if(allocated) 
      cudaFree(devicePtr_); 
    } 

    cudaError_t allocateDevice() 
    { 
     if(allocated) free(devicePtr_); 
     cudaError_t err = 
      cudaMalloc((void**)&devicePtr_, sizeof(T) * this->size()); 
     allocated = true; 
     return err; 
    } 

    cudaError_t loadToDevice() 
    { 
     return cudaMemcpy(devicePtr_, &(*this)[0], sizeof(T) * this->size(), 
      cudaMemcpyHostToDevice); 
    } 

    cudaError_t loadFromDevice() 
    { 
     return cudaMemcpy(&(*this)[0], devicePtr_, sizeof(T) * this->size(), 
      cudaMemcpyDeviceToHost); 
    } 

    // Accessors 

    inline T* devicePtr() { 
     return devicePtr_; 
    } 

}; 

__global__ void kernel(int* a) 
{ 
    int i = threadIdx.x; 
    printf("%i\n", a[i]); 
} 

int main() 
{ 
    UniversalVector<int> vec(3); 
    vec.at(0) = 1; 
    vec.at(1) = 2; 
    vec.at(2) = 3; 

    vec.allocateDevice(); 
    vec.loadToDevice(); 

    kernel<<<1, 3>>>(vec.devicePtr()); 

    checkCUDAError("Error when doing something"); 

    return 0; 
} 

[1]在CUDA它的主機和設備存儲器,其中主機存儲器是由GPU和設備內存在GPU編程的存儲器訪問的存儲器之間分辨必須將內存從主機移動到GPU並返回。

+1

一般來說,擴展STL容器是一個壞主意。在大多數情況下,通過使用組合併爲實際使用的STL容器中的方法提供簡單的外觀,您會更好。 – 2011-05-24 18:03:51

+0

@Heandel不,我只是希望主機端的代碼更清潔一些。設備上的動態大小的陣列甚至可能嗎? – Nils 2011-05-24 18:06:17

+0

@David Thx的評論,但究竟是什麼可能會導致問題,我只是懶得寫一個包裝[],.at()和resize()..的門面。 – Nils 2011-05-24 18:07:30

回答

5

我看到的最大問題是,它並不能真正幫助管理GPU的一面,而且它在過程中混淆了許多非常重要的信息。

儘管容器類包含有關設備指針是否已分配的信息,但無法知道主容器的內容是否已複製到其保存的GPU內存中,或者GPU內存是否已被複制回到設備。因此,每當您希望在主機或設備代碼中使用容器時,您都必須調用loadToDevice()loadFromDevice()方法。這可能意味着至少在某些時候不必要的PCI-E內存傳輸。而且由於您選擇僅包裝同步CUDA內存複製例程,因此每次執行此操作時都會阻塞主機。

最終,我沒有看到這個想法在很多設計良好的幫助例程中獲得了很多淨收益,這些幫助例程抽象出CUDA API的最醜點並在標準STL類型上運行。

8

你可能想看看Thrust。它爲CUDA代碼提供了一些STL容器。

+0

我問過圖書館嗎?我知道推力和推力對於我的需求來說太重了,它完全抽象了內存分配和設備上的複製,這不是我打算做的。我想要的是一個更清晰的代碼,它不太容易出錯.. – Nils 2011-05-24 17:58:13

+0

那麼這也應該是一個主機端抽象.. – Nils 2011-05-24 18:03:33

+5

你不必使用所有的Thrust。只需#include 並僅使用那些。它們是std :: vector與你正在尋找的擴展的工作相同:你可以用operator =從主機複製到設備。 – harrism 2011-05-25 03:18:39

1

你最好擁有像allocateDeviceloadToDevice這些函數作爲自由函數,而不是從std::vector繼承的類的成員。它可以爲您節省大量的與其他庫/類集成在一起的東西。整體看起來不錯。

+1

借調。只要你可以分配設備()並在設備和主機表示之間進行復制(例如使用迭代器),它們就可以成爲任何容器的模板。 – berkus 2011-05-24 18:30:05

2

我將擴展戴維·羅德里格斯 - dribeas評論了一下:

爲什麼你應該更喜歡在組成繼承(即使它需要額外的外牆工作)已被要求,並回答了多次的問題。一個很好的答案是這樣的: Prefer composition over inheritance?

決定性的因素是接口:你想要所有或一些底層類的方法嗎?

在這種修改後的矢量大小有如resizepush_backpop_backeraseinsert,等你的情況std::vector方法可能會導致混亂,如果所謂的loadToDeviceloadFromDevice通話之間。

在你的問題你說,你需要一個包裝原始數組。那些是固定的大小!因此,您可能會很好地在內部包裝類中使用std::vector(組合!),但你需要隱藏它的所有動態大小的東西。