CUDA并行计算 | CUDA算法效率提升关键点概述
前言
CUDA算法的效率总的来说,由存取效率和计算效率两类决定,一个好的CUDA算法必定会让两类效率都达到最优化,而其中任一类效率成为瓶颈,都会让算法的性能大打折扣。
存取效率
存取效率即GPU和显存之间的数据交换效率,在上一篇博客中,我们介绍了GPU的存储结构,对GPU的各类存储介质有了一个初步的了解,其中全局内存具有最大的容量和最慢的访问效率,且对是否对齐和连续访问很敏感,这也是我们在前面推荐进行内存对齐的原因;共享内存访问速度快,且对是否对齐和连续访问不敏感,但是对Bank Conflict非常敏感,Bank Conflict的影响本文在后面会详细介绍,灵活使用共享内存会获得很高的存取效率,也是众多优秀CUDA算法替代全局内存的不二选择;寄存器具有最快的访问速度,只对每个线程可见,线程内多使用寄存器是良好的习惯,但是需要注意一个SM内的寄存器数量有限,当单个线程的寄存器数量超过限制,会影响线程的实际占用率,从而影响加速效果;其他存储介质如纹理内存常量内存等较共享内存和寄存器,在速度并没有太大优势,但是其具有的一些特殊特性使其有时候在特定的情况下被使用以获得更高的效率,比如纹理内存带有的纹理缓存具备硬件插值特性,可以实现最邻近插值和线性插值,且针对二维空间的局部性访问进行了优化,所以通过纹理缓存访问二维矩阵的邻域会获得加速,这个特性使得纹理内存在一些图像处理算法中具有一定的优势。
计算效率
计算效率就是指除去内存交换过程以外的算法计算部分的效率,GPU中主要有三类基础运算:整数运算、单精度浮点数运算和双精度浮点数运算,其中单精度浮点运算速度最快而双精度浮点运算速度最慢,FLOPS(floating-point operations per second, 每秒执行的浮点运算次数)也是衡量GPU运算性能的关键指标,如果一个程序内只有单精度浮点数运算,将发挥硬件的最大功效,因此应该尽量多使用单精度浮点数运算,而避免使用双精度浮点运算。实际上,GPU的单核运算性能远不及CPU,因为单核运算速度取决于核心频率,而GPU的核心频率远不及CPU,目前主流的英特尔第七代桌面级CPU的核心频率都在3.5~4GHz左右,并支持超频,而NVIDIA在2016年发布的号称地球最快显卡NVIDIA TITAN X的核心频率也不过是1.4GHz,和CPU差距依然较大。但是GPU的核心数是CPU所完全无法比拟的,其并行计算效率一般情况下远远大于CPU的单核甚至多核计算效率,核心数的优势让GPU的浮点运算效率远高于CPU,所以对GPU程序来说,让GPU利用率达到100%,让每个线程都处于活动状态,对提高程序的性能有着至关重要的作用。此外,在提高CPU利用率的同时,还必须关注另一个因素:分支(if、else、for、while、do、switch等语句)对计算效率的影响,由于硬件每次只能为一个线程束获取一条指令,若线程束中一半的线程要执行条件为真的代码段,一半线程要执行条件为假的代码段,这时有一半的线程会被阻塞,而另一半线程会执行满足条件的那个分支,如此,硬件的利用率只达到了50%,大大影响并行性能。
性能优化要点
在基于CUDA优化算法设计过程中,除了使算法能够运行得到正确结果之外,更重要的是算法效率能达到理想的水平,而从上面的描述来看,要发挥CUDA算法的性能优势必须考虑全面,留意一些性能陷阱,采用合理的算法设计方案。一般来说,优化一个CUDA算法的性能需要专注三个方面,按照重要性排序为:
展现足够的并行性
为了最大程度的利用GPU多线程的优势,应该在GPU上安排尽量多的并发任务,以使指令带宽和内存带宽都达到饱和,在一个SM(流处理器)中保证有足够多的并发线程束,这不单单是要为GPU每个线程都安排任务,还需要检查SM资源占用率的限制因素(共享内存、寄存器以及计算周期等)以找到达到最佳性能的平衡点,因为GPU的内存资源是有限的,为每个线程分配的资源也是有限的,如果算法设计者在一个线程中使用了过多的共享内存或者寄存器,那么并发运行的线程数必然会减少,使得SM资源的实际占用率小于理论占用率;另一方面可以为每个线程/线程束分配更多独立的工作。
优化内存访问
大部分GPU算法的性能瓶颈都在于内存访问速度,由于显存访问的高延迟和低效率,内存访问模式对内核性能有着显著的影响。内存访问优化的目标是最大限度地提高内存带宽的利用率,重点在于优化内存访问模式和保证充足的并发内存访问。在GPU中,线程是以线程束为单位执行的,一个线程束包含32个线程,所以一方面我们最好将并发线程数设置为32的倍数,另一方面当一个线程束发送内存请求(加载或存储)时,都是32个线程一起访问一个设备内存块,因此对于全局内存来说,最好的访问模式就是对齐和合并访问,对齐内存访问要求所需的设备内存的第一个地址是32字节的倍数,合并内存访问指的是通过线程束中的32个线程来访问一个连续的内存块。这表示在算法设计中一定要尽量为一个线程束的线程分配连续的内存块,比如0~31号线程(同一个线程束)访问影像中连续存储的31个像素,而不是访问不连续的31个像素,由于合并访问对内存访问效率影像非常大,所以我们在算法设计中建议严格遵守该要求。
共享内存因为是片上内存,所以比本地和设备的全局内存具有更高的带宽和更低的延迟,使用共享内存有两个主要原因:①减少全局内存的访问次数;②通过重新安排数据布局避免未合并的全局内存的访问。在物理角度上,共享内存通过一种线性方式排列,通过32个存储体(bank)进行访问。Fermi和Kepler架构各有不同的默认存储体模式:4字节存储体模式和8字节存储体模式,共享内存地址到存储体的映射关系随着访问模式的不同而不同,当线程束中的多个线程在同一存储体中访问不同字节时,会发生存储体冲突(Bank Conflict),由于共享内存重复请求,所以多路存储体冲突可能要付出很大的代价,应该尽量避免存储体冲突,每个存储体(Bank)每个周期只能指向一次操作(一个32bit 的整数或者一个单精度的浮点型数据),一次读或者一次写,也就是说每个存储体(Bank)的带宽为每周期 32bit,比如一个32*32的二维单精度浮点数组,每一列属于一个Bank,如果一个线程束里的不同线程访问该数组里同一列的不同数据,则会发生Bank Conflict,解决或减少存储体冲突的一个非常简单有效的方法是填充数组,在合适的位置添加填充字,可以使其跨不同存储体进行访问,从而减少延迟并提高了吞吐量。
寄存器是GPU上最快的存储机制,但是数量非常有限,如果一个线程使用过多的寄存器,会导致SM能够同时启动的线程数变少,实际上很多情况下寄存器都成为了资源占用率无法达到100%的主要限制条件,所以往往要注意监控寄存器的数量,当数量没有超标时,适当的增加数量可以提升性能,而一旦数量超标,最好还是将寄存器的数量减少以保证100%的资源占用率,这可以通过重新排列代码的顺序来实现,比如当变量的赋值和使用靠的很近时,编译器会重复使用少量寄存器以达到减少寄存器数量的目的。
优化指令执行
GPU属于单指令多数据流架构,每个线程束中的所有线程在每一步都执行相同的指令,如果每个指令都能够得到对结果有效的运算值,就能够避免线程的浪费,而如果由于条件分支造成线程束内有不同的控制流路径,则线程运行可能出现分化,这时线程束必须顺序执行每个分支路径,并禁用不在此执行路径上的线程,而如果算法的大部分时间都耗在分支代码中,必然显著的影响内核性能,所以尽量避免使用分支是很关键的,或者尽量使分支有非常大的概率执行对结果有效的哪一个路径。
CUDA并行计算 | CUDA算法效率提升关键点概述的更多相关文章
- win7 64位下自行编译OpenCV2.4.10+CUDA toolkit 5.5的整个过程以及需要注意的问题(opencv+cuda并行计算元素的使用)
		首先说明的是,这个帖子是成功的编译了dll,但是这个dll使用的时候还是很容易出现各种问题的. 发现错误可能是由于系统安装了太多版本的opencv,环境变量的设置混乱,造成dll版本加载 ... 
- 【CUDA 基础】4.1 内存模型概述
		title: [CUDA 基础]4.1 内存模型概述 categories: - CUDA - Freshman tags: - CUDA内存模型 - CUDA内存层次结构 - 寄存器 - 共享内存 ... 
- 【并行计算-CUDA开发】从零开始学习OpenCL开发(一)架构
		多谢大家关注 转载本文请注明:http://blog.csdn.net/leonwei/article/details/8880012 本文将作为我<从零开始做OpenCL开发>系列文章的 ... 
- 【CUDA】CUDA开发环境搭建
		http://blog.csdn.net/tracer9/article/details/50484764 标签: CUDA并行计算NVIDIAlinux 2016-01-08 18:35 637人阅 ... 
- jQuery效率提升建议
		jQuery简洁通用的方法集把编码者从繁重的工作中解脱出来,也拉低了进入javascript的门槛,初学者对浏览器兼容性一无所知的情况下,几行代码就可以写出超炫的特效.网上有一篇文章转载比较泛滥,已经 ... 
- 十条jQuery代码片段助力Web开发效率提升
		JQuery是继prototype之后又一个优秀的Javascript库.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Oper ... 
- paip.提升效率---提升绑定层次--form绑定取代field绑定
		paip.提升效率---提升绑定层次--form绑定取代field绑定 =================== 编辑form中,常常需要,绑定一个对象到个form.. 传统上要绑定field开始. ... 
- web开发中的 emmet 效率提升工具
		web开发中的 emmet 效率提升工具 可以用来快速生成html 代码. 并且给各种IDE.编辑器提供了插件支持,sublime ,webstorm等. 如在webstorm中安装好emmet之后, ... 
- Android studio Debug效率提升
		Android studio Debug效率提升,可以在控制台打印log的同时而不暂停程序的运行,尤其是当遇到复杂交互的时候,比如滑动,拖动,这时候程序暂停执行是特别恶心的.其实你可以更新打印信息而不 ... 
随机推荐
- python输入问题
			1.关于python的输入问题: 在2.x版本单行单输入input,单行多输入raw_input 在3.x版本中就已经没有raw_input,只有input,单行单输入多输入都可以. 类似2 3 4的 ... 
- 行业顶级NoSQL成员坐阵,NoSQL数据库专场重点解析!
			NoSQL数据库作为数据库市场最重要的组成之一,它的一举一动都影响着成千上万的企业.本专场邀请了行业顶级的NoSQL核心成员与大家共同展望NoSQL数据库的未来,阿里巴巴.MongoDB.Rediss ... 
- PHP反序列化漏洞研究
			序列化 序列化说通俗点就是把一个对象变成可以传输的字符串 php serialize()函数 用于序列化对象或数组,并返回一个字符串.序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结 ... 
- Python读取文件时出现UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position xx: 解决方案
			Python在读取文件时 with open('article.txt') as f: # 打开新的文本 text_new = f.read() # 读取文本数据 出现错误: UnicodeDecod ... 
- App响应式布局
			1.手机的响应式布局,所有的单位用rem来表示. 如:设计稿的宽度是750,则html标签的font-size=屏幕宽度/7.5.那么在网页中的尺寸 = 设计高上实际的尺寸/100. 把下面的代码作为 ... 
- 戏说 .NET GDI+系列学习教程(三、Graphics类的应用_自定义控件--主要用于画面拖拽效果)
			如题,需求:在某个图片上用户可以手动指定位置. 如下: 中心思想:仿照Visual Studio工具中的控件的做法 如何仿照呢? 1.自定义的控件类继承System.Windows.Forms.Con ... 
- CPU指令集的虚拟化(x86)
			IA-32 (x86)[edit] Main article: X86 virtualization The IA-32 instruction set of the Pentium processo ... 
- DQL    数据查询语言 select
			1.select 1.select 单独使用 (1) 查询数据库的参数 查看端口: select @@port; 查看数据路径 select @@datadir; (2)调用内置函数 查看当前库 se ... 
- scala 基础笔记
			view bound:必须传入一个隐式转换函数 class [T <% Ordered [T]] content bound:必须传入一个隐式值 class [T : Ordering] !异步 ... 
- 第三记 Java面向对象
			相信很多人都有听到,见到这么一句话:Java是一门面向对象编程的语言,但是又是否对这句话有了自己的理解呢? 一.面向对象 面向对象是一种新兴的程序设计方法,也可以说是一种新的程序设计规范(paradi ... 
