CUDA扩展std :: vector来管理主机和设备数据

时间:2011-05-24 17:49:57

标签: c++ stl cuda software-design

我了解到std :: vector是C ++中原始数组的一个很好的包装器,所以我开始用它来管理我的CUDA app中的主机数据[1]。由于必须手动分配和复制东西会使代码更复杂,更不易读,我想扩展std :: vector。由于我不是很有经验,所以我想知道你对它的看法。特别是天气它正确完成(例如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并返回。

4 个答案:

答案 0 :(得分:8)

您可能需要查看Thrust。它为CUDA代码提供了一些STL容器。

答案 1 :(得分:5)

我看到的最大的问题是,它并没有真正帮助管理GPU方面的事情,并且它在这个过程中混淆了许多非常重要的信息。

虽然容器类包含有关是否已分配设备指针的信息,但无法知道主机容器的内容是否已复制到它所拥有的GPU内存,或者GPU内存是否已被复制回来到设备。因此,每次要在主机或设备代码中使用容器时,都必须调用loadToDevice()loadFromDevice()方法。这可能意味着至少在某些时候不必要的PCI-e内存传输。并且因为您选择仅包装同步CUDA内存复制例程,所以每次执行此操作时都会有主机阻塞。

最终,我没有看到这个想法在一个精心设计的辅助例程集中获得了很多净收益,这些例程抽象出了CUDA API中最丑陋的部分并在标准STL类型上运行。

答案 2 :(得分:2)

我会延长DavidRodríguez - dribeas评论:

为什么你应该更喜欢构图而不是继承(即使它需要额外的外观工作)的问题已被多次询问和回答。这个答案很好: Prefer composition over inheritance?

决定因素是界面:你想要底层类的全部或部分方法吗?

在您的案例std::vector修改向量大小的方法中,例如resizepush_backpop_backeraseinsert,如果在loadToDeviceloadFromDevice之间调用,则可能会造成混乱。

在你的问题中,你声明你需要一个原始数组的包装器。那些是固定的大小!因此,您可以在内部使用包装器类std::vector(组合!),但是您需要隐藏它的所有动态大小。

答案 3 :(得分:1)

最好将allocateDeviceloadToDevice等函数作为自由函数,而不是继承自std::vector的类成员。它可以为您节省大量的其他库/类与您的东西。整体看起来不错。