10. 执行配置优化

● 一个 SM中,占用率 = 活动线程的数量 / 最大可能活动线程的数量。后者保存在设备属性的 maxThreadsPerMultiProcessor 分量中(GTX1070为例,该值为2048)。较高的占用率不代表计算效率很高,但是较低的占用率意味着内存延迟严重,还有改进的空间。

● 寄存器有效性是决定占用率的几个因素之一。寄存器读取比内存读取的延迟低很多,但是寄存器组(register file)数量有限,硬件上被整个 SM 上的所有线程共享,软件上一次只能给一个线程块使用,若线程块使用的寄存器过多,则会减少SM上同时运行的线程块和线程数量,降低占用率。每个线程使用寄存器上限可以在在编译时使用  -maxrregcount  选项进行指定,或每内核使用限定符  __launch_bounds__  。

● 寄存器限制的计算过程。以我的 GTX1070(CC = 6.1)为例,影响SM中活动线程块数量的因素有以下这些。参考了 CUDA C Programming Guide,其他教材或博客上都不完整。

  

■ 当给定一个设备和程序时,首先应该自然满足:tw = 32,tb ≤ tbmax,rt ≤ rtmax,rb = rt·tb ≤ rbmax,sb ≤ sbmax,不然核函数直接报错退出。

■ 考虑到线程数(或等价的线程束数)、寄存器数、共享内存大小这三个方面,需要满足下式条件(第二行与第三行等价,一个以线程的角度考虑,一个以线程束的角度考虑),

■ 考虑 bs 在各约束下的上界,得到一个SM上活跃线程块的解:

■ 于是占用率可用下式计算,(两个算法等价,一个以线程的角度考虑,一个以线程束的角度考虑),第二个算法占用率计算器 CUDADocumentation_tools_CUDA_Occupancy_Calculator.xls 中才用得到算法。

■ 计算案例:tsmax = 768,rsmax = 8192,tb = 128,rt = 12,不考虑 sb 。解答:tsmax / tb = 6,rsmax / (rt * tb) = 5,blockactive = 5,γ1 = 83 %

■ 补充,CUDADocumentation_tools_CUDA_Occupancy_Calculator.xls 给出了更好的计算结果,可以在指定计算能力的情况下计算各种 tb,rt,sb 组合的占用率并化成了图。另外涉及了几个在上述计算中没有考虑的细节:Register allocation unit size = 256(寄存器申请数量基数),Register allocation granularity = warp(寄存器申请粒度,都是 warp),Shared Memory allocation unit size = 256(共享内存申请基数),Warp allocation granularity = 4(warp 数量基数),这样就会引入一堆取整函数,针对每种稀奇古怪的 tb,rt,sb组合都能算出占用率。

● nvcc 命令中使用选项  --ptxas options=v  来规定每个线程使用的寄存器数量。

● 启用线程网格时的一些启示。

■ 线程块中的线程数应该取作线程束尺寸的倍数,以便充分利用内存合并读写,避免非饱和线程束造成的浪费。

■ 适当减小线程尺寸,使得等待  __syncthreads();  的线程变少,降低内存延迟。另一方面,经验上线程尺寸不应小于 64,128 至 256 为宜,除非线程块数量可以很大 。

■ 网格中的线程块数应该大于 SM 数,使每个 SM 都至少有一个线程块进行计算。如 GTX1070 有 16 个SM,则调用核函数时应尽量使 gridDim.x > 16 (一维)。

■ 高占用率意味着每个线程能够使用的寄存器数量较少,通常占用率大于 50 % 以后继续提高占用率对效率提升帮助不明显,这时增加线程寄存器数量,减小内存读写延迟意义更大。

■ 共享内存大小与线程块大小相关,但一般与线程数无关,应使用一个线程对多个共享内存进行操作。

11. 指令级优化

● 算术指令优化(编译器可以完成)。如 n 为 2 的整数幂,则 i / n 等价于 i >> log2(n),i % n 等价于 i & ( n - 1 ) 。

● 浮点数计算尽量使用内置函数,如使用 rsqrtf( x ) 而不是 1.0f / sqrtf( x );单精度函数,如使用 sqrtf( x ) 而不是 sqrt( x )。

● 避免隐式格式转换。如对 char 和 short 的操作通常会转换成 int,使用没有预设为 float 的浮点参数(默认为 double)使 float 变量转换成 double。

● 绝对值较小的有理型指数运算(如计算 x2/3)可以使用 sqrt()、cbrt()、rsqrt()、rcbrt() 等函数进行组合,计算效率高于 pow() (可计算指数为浮点数的情况);绝对值较小的整形指数运算(如计算 x2、x5)可以使用显式乘法或等价的 inline 函数、宏函数等来完成,计算效率高于pow() 。

● exp2() 、exp2f() 、exp10() 、exp10f() 与 exp() 、expf() 效率接近,替代 pow() 或 powf(),计算效率高一个数量级左右。

● 使用 sinpi() 、sinpif() 替代 sin(π * <expr>) 或 sinf(π * <expr>) ,不仅计算速度快且内含 π 的精度也较高。

● nvcc 命令中调整数据精度的一些选项。 -ftz=true  非初始化的数据都默认为0; -prec-div=false  降低除法精度; -prec-sqrt=false  降低平方根计算精度。

● 使用不同类型的内置函数:functionName(),functionNamef(),__functionNamef() 。如  sin(x);  用于计算双精度正弦, sinf(x); 用于计算单精度正弦, __sinf(x);  用于计算可接受精度损失条件下的单精度正弦。__sinf(x) 比 sinf(x) 损失一些精度,但是速度快一个量级,在直接输出结果、不需要进一步规约或减低结果量级的情况下可以使用)

● nvcc 命令中使用选项  -use_fast_math  来强制转化所有 functionNamef() 为 __functionNamef() 。

12. 控制计算流程

● 避免线程分支,分支尽量在线程束之间进行,而一个线程束内部不分。

● Volta 构架开始支持独立线程调度,可以使那些有 “不依赖于数据的分支” 的线程束中的线程独立运行, 然后使用函数  __syncwarp();  来同步线程束。

● 分支预测,可由编译器部分完成,也可由程序员部分控制(如使用预编译命令  #pragma unroll  来展开循环或者判断)。在程序运行时所有分支指令都会被调度,但只有正确谓词的指令被用上。当分支指令少于某一个阈值的时候,编译器会使用预测指令替代分支指令,减少线程束发散。

● 循环变量使用有符号整数,与编译器优化有关。

● 代码分支时谨慎使用  __syncthreads();  ,如下例所示。

 int imax = (nElement + blockDim.x - ) / blockDim.x * blockDim.x;
// 规定下标 i 的范围,向上对齐到 blockDim.x整数倍,保证不小于 nElement,且所有线程迭代次数相等 for (int i = threadIdx.x; i < imax; i += blockDim.x)// 每次迭代读取 blockDim.x 个下标
{
if (i < nElement) // 控制访问范围,最后一次迭代向下对齐到 nElement
{
...
} __syncthreads(); // 线程同步放到条件语句外 if (i < nElement) // 同步后的下一步工作
{
...
}
}

● (?) Similar care must be taken when invoking__syncthreads();from a device function called from potentially divergent code. A straightforward method of solving this issue is to call the device function from non-divergent code and pass a thread_active flag as a parameter to the device function. This thread_active flag would be used to indicate which threads should participate in the computation inside the device function, allowing all threads to participate in the __syncthreads(); .

13. 应用部署

14. 理解编程环境

● CUDA Driver API 和 CUDA Runtime API 是两套不同的编程接口。Driver API 向后兼容但不向前兼容(对 Driver 有最低版本要求)。

  

● CUDA Runtime

■ 主机 Runtime 环境提供函数功能包括:设备管理、上下文管理、内存管理、代码模块管理、执行控制、纹理引用管理、基于OpenGL和Direct3D的互操作性。Runtime 主要完成了控制内核函数加载、设置内核参数、隐式驱动版本检查、代码初始化、CUDA上下文管理、模块管理、内核的配置、参数传递等过程。
■ 与低级别的 CUDA Driver API相比,CUDA Runtime 简化了管理过程。nvcc 生成的 C/C++代码自动使用 CUDA Runtime。另外,cuBLAS、cuFFT 等工具包也依赖于 Runtime。
  cuda_runtime_api.h  // C 语言的 CUDA Runtime 接口头文件
  cuda_runtime.h    // C++ 语言的 CUDA Runtime 借口头文件,依赖于cuda_runtime_api.h

15. 部署准备

● 在不知道运行 CUDA 应用的设备的情况下,程序应该显式地检查设备和运行状况。包括设备数量,计算能力,以及计算过程中的错误。

● CUDA Runtime API 既不能保证向前兼容,也不能保证向后兼容,所以有时需要在应用发布时附上 Runtime Restribution,用动态或静态链接的方式保证运行环境。同一台设备上可能有多个 Runtime 环境,但只要 Driver 均支持即可。

● 默认条件下 nvcc 使用静态链接,使得源代码少少变大,但是保证了 Runtime 文件能被正确读取到。也可在 nvcc 命令中使用选项  --cudart=shared  来控制动态链接源。

● 有关 Linux ,MacOS,Windows 上动态库的一些内容,看不懂。

16. 部署基础设施工具

● Nvidia-SMI ,NVIDIA系统管理工具。

● NVML,NVIDIA 管理库,用于生成第三方管理工具的接口函数库。

● Cluster Management Tools,集群管理工具。

● Compiler JIT Cache Management Tools,即时编译管理工具。

● CUDA_VISIBLE_DEVICE,CUDA 可视化工具。

CUDA C Best Practices Guide 在线教程学习笔记 Part 2的更多相关文章

  1. CUDA C Best Practices Guide 在线教程学习笔记 Part 1

    0. APOD过程 ● 评估.分析代码运行时间的组成,对瓶颈进行并行化设计.了解需求和约束条件,确定应用程序的加速性能改善的上限. ● 并行化.根据原来的代码,采用一些手段进行并行化,例如使用现有库, ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. 策略模式Strategy

    定义一系列的算法,把他们封装起来,使得算法独立于适用对象. 比如,一个系统有很多的排序算法,但是使用哪个排序算法是客户对象的自有.因此把每一个排序当做一个策略对象,客户调用哪个对象,就使用对应的策略方 ...

  2. Maven在Windows中的配置以及IDE中的项目创建

    Maven在Windows下的配置 1.Maven下载地址:http://maven.apache.org/download.cgi,下载红框里的版本即可. 2.解压到D盘: 3.修改配置文件sett ...

  3. 在CentOS6上利用PXE+Kickstart+Apache+DHCP实现无人值守安装

    在CentOS6上利用PXE+Kickstart+Apache+DHCP实现无人值守安装 1.PXEServer:OS:CentOS6.9IP:172.16.25.69: (1)apache:# mo ...

  4. struts2中的Ajax异步校验

    登录时验证码的异步校验: 1.验证码生成的是图片因此在struts.xml文件里面配置action 时,result标签中type 属性是stream 2.验证码图片的src的值为配置action名字 ...

  5. Elixir游戏服设计五

    在<Elixir游戏服设计一>里提到,按照系统功能划分成app要保证原子性很难, 现在想想也没那么难.保证原子性,无非就是需要某个单点去完成操作.那么选择玩家进程去做原子性工作就可以了. ...

  6. # C语言程序设计预备作业

    一.针对老师和学生是怎样的关系的看法 ==首先我认同邹欣老师的看法,下面也是我自己的一点见解.== #### (1)师生在教育内容的教学上结成授受关系 .从教师与学生的社会角色规定意义上看,在知识上, ...

  7. Win CE 6.0 获取手持机GPS定位1----基础知识 (C#)

    一.GPS全球定位系统的组成 (1)GPS卫星(空间部分) 由沿接近环形的地球轨道运行的24颗卫星组成,位于距地表20200千米的高空,均匀分布在6个轨道面上(每个轨道面4颗),轨道倾角55度.此外, ...

  8. C# 使用NPOI 导出Excel

    NPOI可以在没有安装Office的情况下对Word或Excel文档进行读写操作 下面介绍下NPOI操作Excel的方法 首先我们需要下载NPOI的程序集 下载地址 http://npoi.codep ...

  9. Zeppelin源码

    1.概述 在大数据应用场景中,处理数据分析方面,由于开发者的水平不一样,使用的编程语言也不尽相同,可能会涉及到R.Python.Java.Scala等,数据计算模型也估计不一样,可能涉及的有Spark ...

  10. Android 双击退出程序实现(有侧滑界面)

    大家好,今天带来双击退出程序实现方法,我知道,网上也是有许多关于双击退出程序实现的方法,所以,听见当然是给大家带来不一样的双击退出的实现方法. 首先带来的便是关于onKeyDown和onKeyPres ...