▶ 编程接口。参考 http://chenrudan.github.io/

▶ Runtime API 为高层级管理接口,提供申请和释放设备内存,数据迁移,多 GPU 管理等。Driver API 为较低层级的控制接口,提供 CUDA 上下文(模拟设备主机进程),CUDA 模块(模拟设备动态加载库)等。软件层面 Runtime 比 Driver 封装的更好(Runtime 之上就是封装的更好的 cuFFT 等库)。这两个库的函数都是能直接调用的,Driver API 对底层硬件驱动的控制更直接、更方便,并且向后兼容支持老版本(Runtime 不行),运行速度上与 Runtime 差别不大。Driver API 的函数开头是 cu,而 Runtime API 的函数开头是 cuda,例如(选自《CUDA专家手册:GPU编程权威指南》)Runtime API 函数 cudaMalloc() 与 Driver API 函数 cuMemAlloc(),本质功能相同,其关系如下图所示。一般基于 Driver API 来写程序会比 Runtime API 要复杂。

  

▶ nvcc 编译时负责:主机代码与设备代码分离,编译设备代码,调整扩展主机代码,将主机代码交给其他编译器编译,链接 Runtime 函数,连接主机代码和设备二进制代码生成 PTX 或 .cubin,使用 Driver API,读取和执行 .cubin 代码 或PTX代码。

▶ 运行时编译(Just-in-Time Compilation,JIT)。是由设备驱动编译生成的,增加了程序的读取时间,但能更自由的加入编译器和设备驱动的新特性,这也是运行编译时不存在同一设备上的程序的唯一方法。

▶ nvcc 指定构架编译的 cubin 代码只能在同构架下向后兼容,不能向前兼容或跨构架兼容。如编译时使用选项  -code=sm_35  产生的代码只能被计算能力不低于 3.5,且不足 4.0 的设备运行。

▶ 设备驱动指定构架编译的 PTX 代码只能向后兼容,不能向前兼容。编译时使用选项 -arch=compute_30  产生的代码只能被计算能力不低于 3.0 的设备运行。

▶ nvcc 编译命令举例。指定生成同时兼容计算能力 3.5 和 5.0 的 cubin,以及计算能力 6.0 的 PTX 。

 nvcc x.cu
-gencode arch=compute_35,code=sm_35
-gencode arch=compute_50,code=sm_50
-gencode arch=compute_60,code=\'compute_60,sm_60\'

▶ 使用宏  __CUDA_ARCH__ 来判断当前设备代码的计算能力。如用参数 -arch=compute_35  编译时,该宏等于 350 。

▶ CUDA 静态库为  cudart.lib  或  cudart.a ,动态库为  cudart.dll  或  cudart.so 。

▶ 线性设备内存支持 40 位的内存空间(1 TB)。

▶ 使用  cudaGetSymbolAddress()  和  cudaGetSymbolSize()  来获取有关常量内存的地址和尺寸。

 //cuda_runtime_api.h
extern __host__ cudaError_t CUDARTAPI cudaGetSymbolAddress(void **devPtr, const void *symbol); extern __host__ cudaError_t CUDARTAPI cudaGetSymbolSize(size_t *size, const void *symbol);

● 示例代码:

 #include <stdio.h>
#include <malloc.h>
#include <cuda_runtime_api.h>
#include "device_launch_parameters.h" __constant__ float constData[]; int main()
{
float *ptr;
size_t size; cudaGetSymbolAddress((void **)&ptr,constData);
cudaGetSymbolSize(&size, constData); printf("\n\t%u, %u\n", ptr, size);
getchar();
return ;
}

● 输出结果:

        , 

▶ 异构同步执行。

● CUDA中有五项任务是可以同步进行的:主机计算,设备计算,从主机到设备的内存拷贝,从设备到主机的内存拷贝,设备内部的内存拷贝,设备之间的内存拷贝

● 对主机来说设备上的五项任务是可以同步进行的:启用核函数,设备内部的内存拷贝,主机到设备的 64KB 以下的内存拷贝,Async 型的内存拷贝,内存管理类函数的调用。

● 可以通过设定环境值 CUDA_LAUNCH_BLOCKING = 1(没找到定义在那里?)来禁用同步计算(仅用于调试,否则大幅降低程序效率)。

● 使用程序分析器(XX Profiler)时核函数取消同步执行,涉及页锁定内存的拷贝时取消同步执行。

● 拥有不同上下文的两个核函数不能同步执行。

● 使用编译选项  --default-stream per-thread  或者在一包括 cuda 头文件之前定义宏  CUDA_API_PER_THREAD_DEFAULT_STREAM  可以使得主机每一条线程都有各自独立的默认流。

● 使用编译选项  --default-stream legacy  使得每一个设备都有一条称为 NULL stream 的默认流。

● 各种同步函数

■  cudaDeviceSynchronize() ,等待所有流中的任务都结束。

■  cudaStreamSynchronize(); ,等待指定流中的任务全部完成,同时允许其他流中的任务继续运行。

■  cudaStreamWaitEvent(); ,等待指定的流中指定的事件完成。给定流编号可以是0,使得所有流都等待给定的事件完成。

■  cudaStreamQuery(); ,判断给定的流中是否所有任务都已经完成。

▶ 当主机线程在流中使用以下操作时,不同的流之间不能并行执行(发生隐式同步):页锁定内存申请,设备内存申请,设备内存拷贝,设备内部内存拷贝,对 NULL 流的 CUDA 命令,L1 缓存与共享内存交换。

▶ (?) For devices that support concurrent kernel execution and are of compute capability 3.0 or lower, any operation that requires a dependency check to see if a streamed kernel launch is complete: Can start executing only when all thread blocks of all prior kernel launches from any stream in the CUDA context have started executing; Blocks all later kernel launches from any stream in the CUDA context until the kernel launch being checked is complete.
▶ 当流中核函数开始执行且该流中调用了 cudaStreamQuery() 函数时,流中任何其他操作都会要求依赖性检查。所以,为了提升程序的潜在并行执行效率,程序应该遵循两条规则:有依赖性的操作应该放到独立操作之后,尽量延迟使用任何种类的同步

▶ 操作重叠。对比了两种异步并行模式。

 // 第一种,两部分任务执行顺序不能重叠(第 1 任务的 HostToDevice 不能发生在第 0 任务的 DeviceToHost 之前)
for (int i = ; i < ; ++i)
{
cudaMemcpyAsync(d_in + i * size, h_data + i * size, size, cudaMemcpyHostToDevice, stream[i]);
MyKernel << < >> > (d_out + i * size, d_in + i * size, size);
cudaMemcpyAsync(h_data + i * size, d_out + i * size, size, cudaMemcpyDeviceToHost, stream[i]);
} // 第二种,两部分任务执行顺序可以重叠
for (int i = ; i < ; ++i)
cudaMemcpyAsync(d_in + i * size, h_data + i * size, size, cudaMemcpyHostToDevice, stream[i]);
for (int i = ; i < ; ++i)
MyKernel << < >> > (d_out + i * size, d_in + i * size, size);
for (int i = ; i < ; ++i)
cudaMemcpyAsync(h_data + i * size, d_out + i * size, size, cudaMemcpyDeviceToHost, stream[i]);

▶ 回调函数中尽量不使用 CUDA API 函数,否则可能陷入 API -> callback -> API -> callback 的死循环中。

▶ 流的优先级。优先级较高的流中的任务会被优先调度。

     // driver_types.h
#define cudaStreamDefault 0x00 // 默认流标志
#define cudaStreamNonBlocking 0x01 // 不与 NULL 流同步标志 // cuda_runtimt_api.h
extern __host__ __cudart_builtin__ cudaError_t CUDARTAPI cudaStreamCreateWithPriority(cudaStream_t *pStream, unsigned int flags, int priority);
extern __host__ __cudart_builtin__ cudaError_t CUDARTAPI cudaDeviceGetStreamPriorityRange(int *leastPriority, int *greatestPriority); // 使用方法
int priority_high, priority_low;
cudaStream_t st_high, st_low; cudaDeviceGetStreamPriorityRange(&priority_low, &priority_high);
cudaStreamCreateWithPriority(&st_high, cudaStreamNonBlocking, priority_high);
cudaStreamCreateWithPriority(&st_low, cudaStreamNonBlocking, priority_low);

▶ (?)调用同步函数时,控制权转交主机线程,通过先前定义的  cudaSetDeviceFlags();  来决定主机线程是否要抛弃、阻塞还是旋转处理。

▶ 流不能跨设备执行。选定设备  cudaSetDevice(); 以后(单设备情况下不用指定)创建的流默认在该设备上运行。需要创建在另一台设备上的流时,应该重新选定设备然后再创建。流中任务可以跨设备,例如可以在一台设备的流中指定发生在另一台设备上内存拷贝操作。每台设备都有自身的 NULL 流,各台设备的 NULL 可以并行执行。

▶ 与流和时间相关的函数性质。

● 当指定的流和时事件不在同一台设备上时  cudaEventRecord() 运行失败;但  cudaStreamWaitEvent()  仍可以运行成功,可以用于多设备之间的同步。

● 当指定两个事件不在同一台设备上时  cudaEventElapsedTime() 运行失败

● 当指定两个事件在同一台设备上(就算不是当前选定的设备)时, cudaEventSynchronize()  和  cudaEventQuery()  仍可以运行成功。

▶ 设备间通讯,两台设备之间可以直接进行内存访问。

 // cuda_runtime_api.h
extern __host__ cudaError_t CUDARTAPI cudaDeviceCanAccessPeer(int *canAccessPeer, int device, int peerDevice);
extern __host__ cudaError_t CUDARTAPI cudaDeviceEnablePeerAccess(int peerDevice, unsigned int flags);
extern __host__ cudaError_t CUDARTAPI cudaDeviceDisablePeerAccess(int peerDevice); // 使用方法
{
cudaSetDevice(); // 正常使用 0 号设备
float* p0;
cudaMalloc(&p0, sizeof(float)*);
myKernel <<< , >>>(p0); cudaSetDevice(); // 选择 1 号设备 cudaDeviceEnablePeerAccess(, ); // 允许 0 号设备的 P2P 通讯 myKernel <<< , >>>(p0); // 可以在 1号设备上直接使用 0 号设备的内存进行运算 cudaDeviceDisablePeerAccess(); // 禁用 0 号设备的 P2P 通讯
}

● 设备之间的内存拷贝不能使用 cudaMemcpy(),而应该使用配套的 cudaMemcpyPeer(),cudaMemcpyPeerAsync(),cudaMemcpy3DPeer(),cudaMemcpy3DPeerAsync() 等

● 使用 NULL 流在两台设备之间进行内存拷贝时,会要求两设备先前任务全部执行完毕,且在拷贝完成以前不会继续执行后面的任务。

● 不同流的任务重叠性质在两台设备之间进行异步内存拷贝时也可以存在。

● 设备间内存拷贝可以不通过主机内存作为中介,运行速度较快。

 // driver_types.h
struct __device_builtin__ cudaMemcpy3DPeerParms// 相对 cudaMemcpy3DParms 结构仅少了拷贝类型变量 enum cudaMemcpyKind kind;
{
cudaArray_t srcArray;
struct cudaPos srcPos;
struct cudaPitchedPtr srcPtr;
int srcDevice; cudaArray_t dstArray;
struct cudaPos dstPos;
struct cudaPitchedPtr dstPtr;
int dstDevice; struct cudaExtent extent;
}; // cuda_runtime_api.h
extern __host__ cudaError_t CUDARTAPI cudaMemcpyPeer(void *dst, int dstDevice, const void *src, int srcDevice, size_t count);
extern __host__ cudaError_t CUDARTAPI cudaMemcpyPeerAsync(void *dst, int dstDevice, const void *src, int srcDevice, size_t count, cudaStream_t stream __dv());
extern __host__ cudaError_t CUDARTAPI cudaMemcpy3DPeer(const struct cudaMemcpy3DPeerParms *p);
extern __host__ cudaError_t CUDARTAPI cudaMemcpy3DPeerAsync(const struct cudaMemcpy3DPeerParms *p, cudaStream_t stream __dv());

▶ 进程通讯。主机创建的设备指针和事件句柄可以被同一进程内的所有线程共享,但对于其他进程中的线程来说是无效的。可以使用进程通讯接口(Inter Process Communication API, IPC API)来共享指针和句柄信息,目前只在Linux系统上支持。可以使用 cudaIpcGetMemHandle()  来将句柄传递给另一个进程,使用  cudaIpcOpenMemHandle()  来检索给定的句柄下的设备指针。该通讯通常用于主进程任务发布和其他进程的任务执行。

▶ 错误信息检查。

● 一般 cuda 函数都会返回错误代码,使用  cudaGetLastError()  可以捕获错误代码,并将错误信息重置为 cudaSuccess 。

● 异步函数报错比较特殊,因为它执行完毕的时刻不固定(相对于完整的核函数调用或是 runtime 函数调用),需要使用额外、独立的 runtime 函数来检查和报告错误。 cudaDeviceSynchronize()  或其他同步函数可以用于检查异步函数错误。

● 调用核函数不会返回错误代码,需要在调用之后立即使用 cudaGetLastError() 或 cudaPeekAtLastError() 来检查是否存在错误。前者将系统的全局错误信息变量重置为 cudaSucces,后者不会变更。

 // 错误相关函数,cuda_runtime_api.h
extern __host__ __cudart_builtin__ cudaError_t CUDARTAPI cudaGetLastError(void);
extern __host__ __cudart_builtin__ cudaError_t CUDARTAPI cudaPeekAtLastError(void);
extern __host__ __cudart_builtin__ const char* CUDARTAPI cudaGetErrorName(cudaError_t error);
extern __host__ __cudart_builtin__ const char* CUDARTAPI cudaGetErrorString(cudaError_t error); // 使用方法举例
cudaError_t error;
error = cudaGetLastError(); // 获取错误并将系统全局错误信息重置为 cudaSucces
// error = cudaPeekAtLastError(); // 获取错误,不变更全局错误信息
printf("%s", cudaGetErrorName(error)); // 仅报告错误名称
printf("%s", cudaGetErrorString(error));// 报告错误名称和所在函数

▶ 堆栈调用。使用  cudaDeviceGetLimit()  和  cudaDeviceSetLimit()  来设定设备的堆栈大小,当堆栈溢出时会抛出栈溢错误或核函数运行失败。

 // cuda_runtime_api.h
extern __host__ __cudart_builtin__ cudaError_t CUDARTAPI cudaDeviceGetLimit(size_t *pValue, enum cudaLimit limit);
extern __host__ cudaError_t CUDARTAPI cudaDeviceSetLimit(enum cudaLimit limit, size_t value);

CUDA C Programming Guide 在线教程学习笔记 Part 1的更多相关文章

  1. CUDA C Programming Guide 在线教程学习笔记 Part 5

    附录 A,CUDA计算设备 附录 B,C语言扩展 ▶ 函数的标识符 ● __device__,__global__ 和 __host__ ● 宏 __CUDA_ARCH__ 可用于区分代码的运行位置. ...

  2. CUDA C Programming Guide 在线教程学习笔记 Part 4

    ▶ 图形互操作性,OpenGL 与 Direct3D 相关.(没学过,等待填坑) ▶ 版本号与计算能力 ● 计算能力(Compute Capability)表征了硬件规格,CUDA版本号表征了驱动接口 ...

  3. CUDA C Programming Guide 在线教程学习笔记 Part 2

    ▶ 纹理内存使用 ● 纹理内存使用有两套 API,称为 Object API 和 Reference API .纹理对象(texture object)在运行时被 Object API 创建,同时指定 ...

  4. CUDA C Programming Guide 在线教程学习笔记 Part 10【坑】

    ▶ 动态并行. ● 动态并行直接从 GPU 上创建工作,可以减少主机和设备间数据传输,在设备线程中调整配置.有数据依赖的并行工作可以在内核运行时生成,并利用 GPU 的硬件调度和负载均衡.动态并行要求 ...

  5. CUDA C Programming Guide 在线教程学习笔记 Part 13

    ▶ 纹理内存访问补充(见纹理内存博客 http://www.cnblogs.com/cuancuancuanhao/p/7809713.html) ▶ 计算能力 ● 不同计算能力的硬件对计算特性的支持 ...

  6. CUDA C Programming Guide 在线教程学习笔记 Part 9

    ▶ 协作组,要求 cuda ≥ 9.0,一个简单的例子见 http://www.cnblogs.com/cuancuancuanhao/p/7881093.html ● 灵活调节需要进行通讯的线程组合 ...

  7. CUDA C Programming Guide 在线教程学习笔记 Part 8

    ▶ 线程束表决函数(Warp Vote Functions) ● 用于同一线程束内各线程通信和计算规约指标. // device_functions.h,cc < 9.0 __DEVICE_FU ...

  8. CUDA C Programming Guide 在线教程学习笔记 Part 7

    ▶ 可缓存只读操作(Read-Only Data Cache Load Function),定义在 sm_32_intrinsics.hpp 中.从地址 adress 读取类型为 T 的函数返回,T ...

  9. CUDA C Programming Guide 在线教程学习笔记 Part 3

    ▶ 表面内存使用 ● 创建 cuda 数组时使用标志 cudaArraySurfaceLoadStore 来创建表面内存,可以用表面对象(surface object)或表面引用(surface re ...

随机推荐

  1. Linux下rsync命令使用总结

    一.rsync的概述 rsync是类unix系统下的数据镜像备份工具,从软件的命名上就可以看出来了——remote sync.rsync是Linux系统下的文件同步和数据传输工具,它采用“rsync” ...

  2. python lambda匿名函数

    Python的一个很重要的方面就是:函数式编程(functional programming),即可以再原本传递参数和值的地方传递函数. lambda x: x%3 == 0 和以下等价: def b ...

  3. 实习第一周第一天:接口 extends是继承类,implement是实现接口,原接口里面的方法填充,方法名也是不变,重写override是父类的方法名不变,把方法体给改了

    一.定义 Java接口(Interface),是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为( ...

  4. 进程间通信--POSIX信号量

    1.未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而 ...

  5. windows 版nginx 的一些基础知识

    nginx的Windows版本使用原生Win32 API(非Cygwin模拟层).当前nginx/Windows只使用select作为通知方法,所以不要期待它有很高的性能和扩展性.鉴于这点和一些已知问 ...

  6. JMeter连接数据库(查询出的数据作为参数)

    针对Mysql jdbc:mysql://ip:3306/数据库名?useUnicode=true&characterEncoding=utf8&allowMultiQueries=t ...

  7. FastAdmin 系统配置中添加选项卡

    群里有小伙伴问怎么在系统配置中添加选卡,之前试过. 流程如下 刷新页面,然后就有了.

  8. 使用 ASMCMD 工具管理ASM目录及文件

    ============================== -- 使用ASMCMD 工具管理ASM目录及文件 --============================== 在ASM实例中,所有的 ...

  9. tyvj1172自然数拆分

    题目:http://www.joyoi.cn/problem/tyvj-1172 非常水的完全背包.物品就是1~n这n个数. 第6行有橙色的警告:this decimal constant is un ...

  10. BASIC-14_蓝桥杯_时间转换

    示例代码: #include <stdio.h> int main(void){ int t = 0 , h = 0 , m = 0 , s = 0 ; scanf("%d&qu ...