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的同时而不暂停程序的运行,尤其是当遇到复杂交互的时候,比如滑动,拖动,这时候程序暂停执行是特别恶心的.其实你可以更新打印信息而不 ...
随机推荐
- 循环神经网络(Recurrent Neural Network)
传统的神经网络模型,隐藏层的节点之间是无连接的,如下图所示. 而循环神经网络隐藏层的节点之间有连接,主要用于对序列数据进行分类.预测等处理.有连接意味着需要接受信息,这种网络通常用来对序列数据进行处理 ...
- hdu 3450 后缀数组
题目大意: 求多个字符串的最长公共子串 基本思路: 参加我的博客hdu2774 代码如下: #include<cstdio> #include<cstring> using n ...
- 批量修改root密码
公司有五十多台服务器.每台服务器中使用的密码完全不同,同时操作系统也不一样,centos5,6,7 .ubuntu,windows都有,更不用提其中各种小版本. root密码定期更改是一个大问题(wi ...
- SQL 查询子句
SQL WHERE Clause(查询子句) WHERE 子句用于过滤记录. SQL WHERE 子句 WHERE子句用于提取满足指定标准的记录. SQL WHERE 语法 SELECT column ...
- 【Nacos】本地集群部署
关于Nacos已经展开了四篇入门文章: 初探Nacos(一)-- 单机模式启动 初探Nacos(二)-- SpringCloud使用Nacos的服务注册与发现 初探Nacos(三)-- SpringB ...
- PHP FILTER_VALIDATE_INT 过滤器
定义和用法 FILTER_VALIDATE_INT 过滤器把值作为整数来验证. Name: "int" ID-number: 257 可能的选项或标志: min_range - 规 ...
- Shiro学习(8)拦截器机制
8.1 拦截器介绍 Shiro使用了与Servlet一样的Filter接口进行扩展:所以如果对Filter不熟悉可以参考<Servlet3.1规范>http://www.iteye.com ...
- 配置Redis集群为开机自启动
vim /etc/init.d/redisc 将下方脚本写入redisc文件中 #!/bin/sh # chkconfig: 2345 80 90 # # Simple Redis init.d sc ...
- MFC的回调函数
MFC中应该有两类回调函数:一类是源自C的传统回调函数,此类回调函数若非定义为全局函数,而定义在类中的话,要添加static约束,常见的有EnumXXX():一类是消息响应函数,通过成员函数指针实 ...
- PHP面试 PHP基础知识 九(面向对象)
面向对象 PHP的类权限控制修饰符 public(公共的) . protected(受保护的).private(私有的) public :最高权限 可以在类的内部使用 可以在类的外部使用 可以 ...