本文转自:http://blog.163.com/wujiaxing009@126/blog/static/71988399201701224540201/

1、引言

CUDA性能优化----sp, sm, thread, block, grid, warp概念中提到:逻辑上,CUDA中所有thread是并行的,但是,从硬件的角度来说,实际上并不是所有的thread能够在同一时刻执行,接下来我们将深入学习和了解有关warp的一些本质。
 

2、Warps and Thread Blocks

warp是SM的基本执行单元。一个warp包含32个并行thread,这32个thread执行于SIMT模式。也就是说所有thread执行同一条指令,并且每个thread会使用各自的data执行该指令。
block可以是1D、2D或者3D的,但是,从硬件角度看,所有的thread都被组织成一维的,每个thread都有个唯一的ID。每个block的warp数量可以由下面的公式计算获得:

一个warp中的线程必然在同一个block中,如果block所含线程数目不是warp大小的整数倍,那么多出的那些thread所在的warp中,会剩余一些inactive的thread,也就是说,即使凑不够warp整数倍的thread,硬件也会为warp凑足,只不过那些thread是inactive状态,需要注意的是,即使这部分thread是inactive的,也会消耗SM资源,这点是编程时应避免的。

3、Warp Divergence(warp分歧)

控制流语句普遍存在于各种编程语言中,GPU支持传统的、C-style的显式控制流结构,例如if…else,for,while等等。
CPU有复杂的硬件设计可以很好的做分支预测,即预测应用程序会走哪个path分支。如果预测正确,那么CPU只会有很小的消耗。和CPU对比来说,GPU就没那么复杂的分支预测了。
这样问题就来了,因为所有同一个warp中的thread必须执行相同的指令,那么如果这些线程在遇到控制流语句时,如果进入不同的分支,那么同一时刻除了正在执行的分支外,其余分支都被阻塞了,十分影响性能。这类问题就是warp divergence。
注意,warp divergence问题只会发生在同一个warp中。下图展示了warp divergence问题:
为了获得最好的性能,就需要避免同一个warp存在不同的执行路径。避免该问题的方法很多,比如这样一个情形,假设有两个分支,分支的决定条件是thread的唯一ID的奇偶性,kernel函数如下(simpleWarpDivergence.cu):

__global__ void mathKernel1(float *c)

{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
float a, b;
a = b = 0.0f;
if (tid % 2 == 0)
a = 100.0f;
else
b = 200.0f;
c[tid] = a + b;
}

一种方法是,将条件改为以warp大小为步调,然后取奇偶,代码如下:

__global__ void mathKernel2(void)

{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
float a, b;
a = b = 0.0f;
if ((tid / warpSize) % 2 == 0)
a = 100.0f;
else
b = 200.0f;
c[tid] = a + b;
}

通过测试发现两个kernel函数性能相近,到这里你应该在奇怪为什么二者表现相同呢,实际上是因为当我们的代码很简单,可以被预测时,CUDA的编译器会自动帮助优化我们的代码。(稍微提一下GPU分支预测,这里一个被称为预测变量的东西会被设置成1或者0,所有分支都会执行,但是只有预测变量值为1时,该分支才会得到执行。当条件状态少于某一个阈值时,编译器会将一个分支指令替换为预测指令。)因此,现在回到自动优化问题,一段较长的代码就可能会导致warp divergence问题了。
可以使用下面的命令强制编译器不做优化:
$ nvcc -g -G -arch=sm_20 simpleWarpDivergence.cu -o simpleWarpDivergence

4、Resource Partitioning(资源划分)

一个warp的context包括以下三部分:
  1. Program counter
  2. Register
  3. Shared memory
再次重申,在同一个执行context中切换是没有消耗的,因为在整个warp的生命期内,SM处理的每个warp的执行context都是“on-chip”的。
每个SM有一个32位register集合放在register file中,还有固定数量的shared memory,这些资源都被thread瓜分了,由于资源是有限的,所以,如果thread数量比较多,那么每个thread占用资源就比较少,反之如果thread数量较少,每个thread占用资源就较多,这需要根据自己的需求作出一个平衡。
资源限制了驻留在SM中blcok的数量,不同的GPU,register和shared memory的数量也不同,就像Fermi和Kepler架构的差别。如果没有足够的资源,kernel的启动就会失败。下图是计算能力为2.x和3.x的device参数对比:

当一个block获得到足够的资源时,就成为active block。block中的warp就称为active warp。active warp又可以被分为下面三类:

  1. Selected warp
  2. Stalled warp
  3. Eligible warp
SM中warp调度器每个cycle会挑选active warp送去执行,一个被选中的warp称为Selected warp,没被选中,但是已经做好准备被执行的称为Eligible warp,没准备好要被执行的称为Stalled warp。warp适合执行需要满足下面两个条件:
  1. 32个CUDA core有空
  2. 所有当前指令的参数都准备就绪
例如,Kepler架构GPU任何时刻的active warp数目必须少于或等于64个。selected warp数目必须小于或等于4个(因为scheduler有4个?不确定,至于4个是不是太少则不用担心,kernel启动前,会有一个warmup操作,可以使用cudaFree()来实现)。如果一个warp阻塞了,调度器会挑选一个Eligible warp准备去执行。
CUDA编程中应该重视对计算资源的分配:这些资源限制了active warp的数量。因此,我们必须掌握硬件的一些限制,为了最大化GPU利用率,我们必须最大化active warp的数目。
 

5、Latency Hiding(延迟隐藏)

指令从开始到结束消耗的clock cycle称为指令的latency。当每个cycle都有eligible warp被调度时,计算资源就会得到充分利用,基于此,我们就可以将每个指令的latency隐藏于issue其它warp的指令的过程中。
和CPU编程相比,latency hiding对GPU非常重要。CPU cores被设计成可以最小化一到两个thread的latency,但是GPU的thread数目可不是一个两个那么简单。
当涉及到指令latency时,指令可以被区分为下面两种:
  1. Arithmetic instruction
  2. Memory instruction
顾名思义,Arithmetic  instruction latency是一个算术操作的始末间隔。另一个则是指load或store的始末间隔。
二者的latency大约为:
  1. 10-20 cycle for arithmetic operations
  2. 400-800 cycles for global memory accesses
下图是一个简单的执行流程,当warp0阻塞时,执行其他的warp,当warp变为eligible时重新执行。

你可能想要知道怎样评估active warps 的数量来hide latency。Little’s Law可以提供一个合理的估计:

对于Arithmetic operations来说,并行性可以表达为用来hide  Arithmetic latency的操作的数目。下表显示了Fermi和Kepler架构的相关数据,这里是以(a + b * c)作为操作的例子。不同的算术指令,throughput(吞吐)也是不同的。

这里的throughput定义为每个SM每个cycle的操作数目。由于每个warp执行同一种指令,因此每个warp对应32个操作。所以,对于Fermi来说,每个SM需要640/32=20个warp来保持计算资源的充分利用。这也就意味着,arithmetic operations的并行性可以表达为操作的数目或者warp的数目。二者的关系也对应了两种方式来增加并行性:

  1. Instruction-level Parallelism(ILP):同一个thread中更多的独立指令
  2. Thread-level Parallelism (TLP):更多并发的eligible threads
对于Memory operations,并行性可以表达为每个cycle的byte数目。

因为memory throughput总是以GB/Sec为单位,我们需要先作相应的转化。可以通过下面的指令来查看device的memory frequency:

$ nvidia-smi -a -q -d CLOCK | fgrep -A 3 "Max Clocks" | fgrep "Memory"
以Fermi架构为例,其memory frequency可能是1.566GHz,Kepler的是1.6GHz。那么转化过程为:
 
乘上这个92可以得到上图中的74,这里的数字是针对整个device的,而不是每个SM。
有了这些数据,我们可以做一些计算了,以Fermi架构为例,假设每个thread的任务是将一个float(4 bytes)类型的数据从global memory移至SM用来计算,你应该需要大约18500个thread,也就是579个warp来隐藏所有的memory latency。
Fermi有16个SM,所以每个SM需要579/16=36个warp来隐藏memory latency。
 

6、Occupancy(占用率)

当一个warp阻塞了,SM会执行另一个eligible warp。理想情况是,每时每刻到保证cores被占用。Occupancy就是每个SM的active warp占最大warp数目的比例:
我们可以使用cuda库函数的方法来获取warp最大数目:
cudaError_t cudaGetDeviceProperties(struct cudaDeviceProp *prop, int device);
然后用maxThreadsPerMultiProcessor来获取具体数值。
grid和block的配置准则:
  • 保证block中thread数目是32的倍数
  • 避免block太小:每个blcok最少128或256个thread
  • 根据kernel需要的资源调整block
  • 保证block的数目远大于SM的数目
  • 多做实验来挖掘出最好的配置
Occupancy专注于每个SM中可以并行的thread或者warp的数目。不管怎样,Occupancy不是唯一的性能指标,当Occupancy达到某个值时,再做优化就可能不再有效果了,还有许多其它的指标需要调节。
 

7、Synchronize(同步)

同步是并行编程中的一个普遍问题。在CUDA中,有两种方式实现同步:
  1. System-level:等待所有host和device的工作完成
  2. Block-level:等待device中block的所有thread执行到某个点
因为CUDA API和host代码是异步的,cudaDeviceSynchronize可以用来停下CPU等待CUDA中的操作完成:
cudaError_t cudaDeviceSynchronize(void);
因为block中的thread执行顺序不定,CUDA提供了一个函数来同步block中的thread。
__device__ void __syncthreads(void);
当该函数被调用时,block中的每个thread都会等待所有其他thread执行到某个点来实现同步。
 

8、结束语

CUDA性能优化是一个多方面、复杂的问题,深入了解warp的概念和特性是CUDA性能优化的一个关键和开始。

CUDA性能优化----warp深度解析的更多相关文章

  1. 最新IP数据库 存储优化 查询性能优化 每秒解析上千万

    高性能IP数据库格式详解 每秒解析1000多万ip  qqzeng-ip-ultimate.dat 3.0版 编码:UTF8     字节序:Little-Endian 返回规范字段(如:亚洲|中国| ...

  2. Web 前端性能优化相关内容解析

    Web 前端性能优化相关内容,来源于<Google官方网页载入速度检测工具PageSpeed Insights 使用教程>一文中PageSpeed Insights 的相关说明.大家可以对 ...

  3. Web 前端性能优化相关内容解析[转]

    Web 前端性能优化相关内容,来源于<Google官方网页载入速度检测工具PageSpeed Insights 使用教程>一文中PageSpeed Insights 的相关说明.大家可以对 ...

  4. Android性能优化9-ANR完全解析

    1.什么是ANR 在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对 ...

  5. 程序员收藏必看系列:深度解析MySQL优化(二)

    程序员收藏必看系列:深度解析MySQL优化(一) 性能优化建议 下面会从3个不同方面给出一些优化建议.但请等等,还有一句忠告要先送给你:不要听信你看到的关于优化的“绝对真理”,包括本文所讨论的内容,而 ...

  6. Mysql优化深度解析

    说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的工作原 ...

  7. CUDA上的量化深度学习模型的自动化优化

    CUDA上的量化深度学习模型的自动化优化 深度学习已成功应用于各种任务.在诸如自动驾驶汽车推理之类的实时场景中,模型的推理速度至关重要.网络量化是加速深度学习模型的有效方法.在量化模型中,数据和模型参 ...

  8. 让DB2跑得更快——DB2内部解析与性能优化

    让DB2跑得更快——DB2内部解析与性能优化 (DB2数据库领域的精彩强音,DB2技巧精髓的热心分享,资深数据库专家牛新庄.干毅民.成孜论.唐志刚联袂推荐!)  洪烨著 2013年10月出版 定价:7 ...

  9. 秋色园QBlog技术原理解析:性能优化篇:缓存总有失效时,构造持续的缓存方案(十四)

    转载自:http://www.cyqdata.com/qblog/article-detail-38993 文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文 ...

随机推荐

  1. B1041. 考试座位号(15)

    这题比较简单,没有调试,一次通过,虽然简单,不过也有借鉴意义. #include<bits/stdc++.h> using namespace std; const int N=1005; ...

  2. Zip伪加密 破解ZIP密码

    ZIP是一种相当简单的分别压缩每个文件的存档格式.分别压缩文件允许不必读取另外的数据而检索独立的文件:理论上,这种格式允许对不同的文件使用不同的算法.不管用何种方法,对这种格式的一个告诫是对于包含很多 ...

  3. Github与SmartGit使用说明与建议

    当使用github做协同的时候,我们常常需要在客户端安装相应的软件,SmartGit就是一款非常出色的软件,不过是要付费的,我们可以使用non-commercial版本. Download: http ...

  4. classpath与clsspath*

    classpath是指 WEB-INF文件夹下的classes目录 classes含义: 1.存放各种资源配置文件 eg.init.properties log4j.properties struts ...

  5. What is the difference between WinRT, UWP and WPF?

    在学习UWP的过程中确实有这个迷惑,在此分享一下. UWP (Universal Windows platform), Metro and WinRT are all result of Micros ...

  6. 侧边导航栏css示例

    效果展示: html: <div class="sidebar"> <ul> <li>优先级 <ul> <li>< ...

  7. Docker(十一)-Docker commit创建镜像

    创建镜像有很多方法,用户可以从 Docker Hub 获取已有镜像并更新,也可以利用本地文件系统创建一个. 修改已有的镜像 查看已有的镜像: $ sudo docker images REPOSITO ...

  8. Windows下多线程编程(二)

    线程的分类 1.     有消息循环线程 MFC中有用户界面线程,从CWinThread派生出一个新的类作为UI线程类CUIThread,然后调用AfxBeginthread(RUNTIME_CLAS ...

  9. browser-sync & http server

    browser-sync & http server browser-sync https://www.browsersync.io/ usage # step 1 $ npm install ...

  10. 11Java网络编程

    十一.网络编程       11.1 网络通信协议 网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信.这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对 ...