3.11_CUDA运行时与CUDA驱动程序API

3.11 CUDA运行时与CUDA驱动程序API

CUDA运行时提供了方便的语言集成功能,可以使程序很轻松的从零开始。通过自动进行像初始化上下文或加载模块这样的任务,并且用其他的C++代码以内联方式启动内核。CUDART使开发者专注于怎样使代码工作的更快。少数的CUDA抽象,像CUDA模块,在CUDART中并不可用。

另一方面,驱动程序API暴露了所有的CUDA抽象,使开发者可以在应用程序中操作它们。驱动程序API没有提供任何性能优势。但它为应用程序提供了显式的资源管理,这在需要资源管理的应用程序中十分重要,例如拥有插件架构的大规模商业应用。

驱动程序API的运行速度并不比CUDA运行时显著提升,如果你正在寻找改进CUDA应用程序性能的方法,请到其他章节去找。

CUDA的大部分特性对CUDA运行时和驱动程序API都可使用,只有几个例外。表3-9给出了详细信息:

表3-9 CUDA运行时vs驱动程序API

在两种API之间,像内存复制这样的操作趋向于功能性相同,但是接口却全然不同。但流API是几乎相同的。

事件API有一些细微的区别,如果开发者想要指定一个标志词(创建一个阻塞事件时需要),CUDART提供了一个独立的CUDAEventCreateWithFlags()函数。

内存复制函数的接口大部分都是不同的,尽管它们的实际功能是相同的。CUDA支持3种类型的内存——主机内存、设备内存、CUDA数组——内存复制发生在它们的全排列间,并可以发生在1D、2D或3D三种结构上。所以内存复制函数要么包括大量的不同函数,要么包括小数量却能支持不同内存类型的函数。

CUDA中的最简单的内存复制是主机与设备内存间的复制,但是这些函数接口仍然大不相同:CUDART使用void既代表主机指针又能表示设备指针,并且一个内存复制函数接受一个目标参数;而驱动程序API使用void代表主机内存,CUdeviceptr代表设备内存,3个独立的函数(cuMemcpyHtoD()、cuMemcpyDtoH()和cuMemcpyDtoD())对应不同的复制目标。下表给出CUDART和驱动程序API主机与内存间的相关参数列表:

对于2D和3D内存复制,驱动程序API实现了一批接受一个描述结构体并支持所有类型的内存复制的函数,这也支持低维内存复制。例如,如果需要,cuMemcpy3D()可以代替cuMemcpyHtoD()执行1D主机设备间的内存复制:

CUDA_MEMCPY3D cp = {0};  
cp.dstMemoryType = CU_MEMORYTYPE_DEVICE;  
cp.dstDevice = dptr;  
cp.srcMemoryType = CU_MEMORYTYPE_HOST;  
cp.srcHost = host;  
cp.WidthInBytes = bytes;  
cp.Height = cp.Depth = 1;  
status = cuMemcpy3D(&cp);

CUDART使用描述结构体的组合来描述复杂的内存复制函数(例如,CUDAMemcpy3D()),同时使用不同的函数实现不同内存类型的复制。如同cuMemcpy3D()函数,CUDART中的CUDAMemcpy3D()函数可以接受一个描述结构体来描述内存复制的双方,也包括跨维度内存复制(执行1D与2D CUDA数组中的一列之间的复制,或执行2D CUDA数组与3D CUDA数组一个切片之间的复制)。它的描述结构仅有微小改变,这个改变在于另外嵌入了其他结构体。表3-10给出了两个API的3D复制结构体的对照表。

表3-10 3D内存复制结构体

structudaMemcpy3DParams
{
    structudaArray *srcArray;
    structudaPos srcPos;
    structudaPitchedPtr srcPtr;
    structudaArray *dstArray;
    structudaPos dstPos;
    structudaPitchedPtr dstPtr;
    structudaExtent extent;
    enumudaMemcpyKind kind;
};
structudaPos
{
    size_t x;
    size_t y;
    size_t z;
};
structudaPitchedPtr
{
    void *ptr;
    size_t pitch;
    size_t xsize;
    size_t ysize;
};

两者3D内存复制的用法是相似的。它们均被设计为零初始化,并且开发者需要对给定的操作设置需要的成员。例如,执行主机到3D数组间的复制可以按下述步骤执行:

structudaMemcpy3DParams cp = {0};
cp.srcPtr.ptr = host;
cp.srcPtr.pitch = pitch;
cp.dstArray = hArray;
cpextent.width = Width;
cpextent.height = Height;
cpextent.depth = Depth;
cp.kind =udaMemcpyHostToDevice;
status =udaMemcpy3D(&cp);

对于一个覆盖整个CUDA数组的3D复制,源和目标地址偏移量在第一行被设置为0,且只被引用一次。不同于函数的参数,代码只需要引用复制操作需要的参数,而且如果程序执行超过一个相同的操作(例如填充不止一个设备内存区或CUDA数组),这个描述结构体可被复用。