[翻译]CUDA-C-Programming-Guide Maximize InstructionThroughput

为了最大化CUDA-C程序的指令吞吐量,应当避免使用低吞吐量的计算指令,减少分支并降低指令数量。具体策略包括使用精度换取速度,如单精度代替双精度,优化分支条件,利用向量化指令提高性能,以及谨慎处理同步指令。例如,__fdividef用于提升单精度除法性能,rsqrtf提供更快的平方根计算。同步指令如__syncthreads()的执行效率会因设备型号而异。
摘要由CSDN通过智能技术生成

5.4 最大化指令吞吐量

为了达到最大的指令吞吐量,程序应该:

  • 最小化使用低吞吐量的计算指令;有以下方法:在不影响结果的情况下以精度换取执行速度,比如使用指令来代替内置函数,用单精度代替双浮点精度,或者将非归一化数据刷新为0.
  • 最小化分支
  • 减少指令的数量,

本章节中,吞吐量通过每个SM每个时钟周期进行的运算次数来表示。对于一个warp=32,一条指令对应了32次运算,所有如果一个时钟周期进行了N次运算,那么指令吞吐量就是每个时钟周期N/32个指令。

吞吐量是针对一个SM的,将其乘以SM的数量就得到整个设备的吞吐量。

5.4.1 计算指令

下表给出了各种不同设备天然支持的计算吞吐量

   

两外还有些指令和函数在天然指令的顶端实现。对于不同计算能力设备函数实现可能都不相同,不同的编译器版本,生成的指令个数也不相同。对于一些复杂点的函数,根据输入不同会有不同的分支代码。可以用cuobjdump来检查cubin 对象更具体的实现。

在头文件中可以查看这些函数的实现(math_functions.h,device_function.h,…)

一般的,带了-ftz=true(非正规化数据刷新为0)比带-ftz=false 编译选项编译出来的代码有更高的性能。加了-prec div=false(更低的精度)比-prec div=true编译选项编出来的代码有更高的性能。加了-prec-sqrt=false(更低精度的平方根)比-prec-sqrt=true编译选项生成的代码有更高的性能。更多详细的信息可以查看nvcc的用户手册。

单精度浮点除法

__fdividef(x,y)提供了比一般除法运算更高性能的单精度浮点数除法。

单精度浮点对数平方根

在符合IEEE-754的条件下,当对数和平方根近似时,编译器将1.0/sqrtf()优化成rsqrtf(),所以在需要的地方推荐直接调用rsqrtf()。

单精度浮点数平方根

单精度平方根被实现为平方根的倒数的倒数,而不是平方根的倒数后接乘法,因为这样可以得到0或者无穷大的结果。

正玄和余玄函数

Sinf(x),cosf(x),tanf(x),sinconsf(x)和对应的双精度浮点数函数在参数x很大时性能都比较低。

更准确的说,输入的参数决定了函数内部代码的走向:快速的实现还是慢速的实现。

当输入参数小时,运算只是一些简单的乘加运算,函数会执行快速的实现。当输入参数大,就会包括一些很冗长的运算,函数为了得到准确的结果就会执行慢速的实现。

目前来讲,当参数小于105615.0f的单精度时函数执行快速实现;小于2147483648.0的双精度时,函数执行慢速的实现。

因为慢速的实现会比快速的实现使用更多的寄存器,并且尝试减少寄存器的压力,在慢速实现代码中,一些中间临时变量都存在局部存储器,而局部存储器延迟高,带宽低。当前的实现时局部存储器的28字节被单精度函数使用,双精度函数使用局部存储器的44字节。

整数运算

整数运算和模运算是非常耗时的,因为他们需要多达20条指令。在有些情况下,他们可以用位运算代替:如果n是2的次幂,那么1/n等于i>>log2(n),I % n 等于(i&(n-1));

半精度运算

为了获得高的半精度单浮点数加,乘法或者乘加的吞吐量,推荐使用half2数据类型。向量指令如__hadd2,__hsub2,__hmul2,__hfma2 能够在单条指令下执行两次运算。使用half2代替half可以提高性能。

__halves2half2指令可以将两个单精度值转换成half2数据类型。

类型转换

有些时候,编译器必须插入一些转换指令,从而引入额外的执行时钟周期。这些情况包括:

  • char或short 运算,转成int
  • 双精度浮点数常量(没有使用类型后缀)传入参数为单精度浮点数的函数。这种情况可以在定义的时候避免,定义时增加f作为后缀,比如1.0f,0.5f

5.4.2 流程控制指令

由于流程控制指令(if,switch,do,for,while)会导致一个warp中的线程执行不同的路径,所有流程控制指令会严重的影响指令吞吐量。如果这种情况发生,不同的执行路径必须顺序执行,这样就增加了执行的指令数量。

对于流程控制依赖于线程ID的情况,可以通过设计控制条件使得有分叉的warp数量最小,从而获得好的性能。举个简单的例子:如果将控制条件设计成只依赖于threadIdx/warpSize,这样的化,没有分叉的warp,因为控制条件完美的和warp对齐。

有些时候,编译器会展开循环,或者通过分支预判来优化短的if或者switch语句块。编程人员可以使用#pragma unroll语句来控制展开循环。

当使用分支预判时,所有分支的所有指令都不会被跳过,相反的,他们每一个都会附带一个根据控制条件设置成true或false的条件代码,虽然他们都会执行,但是只有设置成true的指令会真正执行,而那些设置成false的指令不会写结果,并且不会计算地址或者读取操作数。

5.4.3 同步指令

__syncthreads()在计算能力为3.x的设备上是每时钟周期128次,6.0上是32每时钟周期32次,7.x上是每个时钟周期16次,5.x,6.1,6.2上每个时钟周期64次。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值