本文转自: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. webstorm开发微信小程序代码提醒(webstorm开发工具)

    使用了微信提供的开发工具是真心难用,卡顿厉害.中英文切写注释换相当不爽.还没办法多开窗口,相信大家也遇到过这种现象. 下边我们介绍下webstorm来开发微信小程序的一些配置: File---sett ...

  2. Linux内核分析作业 NO.2

    操作系统是如何工作的 于佳心 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  ...

  3. Leetcode题库——39.组合总和

    @author: ZZQ @software: PyCharm @file: combinationSum.py @time: 2018/11/14 18:23 要求:给定一个无重复元素的数组 can ...

  4. Linux 信号:signal 与 sigaction

    0.Linux下查看支持的信号列表: france@Ubuntux64:~$ kill -l ) SIGHUP ) SIGINT ) SIGQUIT ) SIGILL ) SIGTRAP ) SIGA ...

  5. alpha冲刺随笔(一)

    项目Alpha冲刺(团队) Alpha冲刺第一天 姓名 学号 博客链接 何守成 031602408 http://www.cnblogs.com/heshoucheng/ 黄锦峰 031602411 ...

  6. eclipse运行tomcat中发生异常重启后tomcat端口被占用

    在任务管理器关闭javaw进程即可,一般此时会有两个以上javaw进程,关闭其中占用内存较少的那个 可用netstat -ano命令查看端口占用情况

  7. HTTTP及TCP的超时以及KEEP-ALIVE机制小结

    一.HTTP的超时和Keep Alive HTTP Keepalive 机制是http 1.1中增加的一个功能. 在HTTP 1.0中,客户端每发起一个http 请求,等收到接收方的应答之后就断开TC ...

  8. bzoj 2243: [SDOI2011]染色 (树链剖分+线段树 区间合并)

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 9854  Solved: 3725[Submit][Status ...

  9. Racket里的方括号

    Racket里的方括号 Racket编程指南 https://blog.csdn.net/chinazhangyong/article/category/7386082 来自于QQ群racket!(  ...

  10. 【题解】 [ZJOI2008] 泡泡堂(贪心/二分图/动态规划)

    懒得复制,戳我戳我 Solution: 就是有一个贪心策略:(以下假设使\(A\)队分数更高) \(First:\)比较两个分值的最小值,如果\(A\)最小分比\(B\)最小分大就直接比较两个最小的, ...