GPU体系架构(一):数据的并行处理
最近在了解GPU架构这方面的内容,由于资料零零散散,所以准备写两篇博客整理一下。GPU的架构复杂无比,这两篇文章也是从宏观的层面去一窥GPU的工作原理罢了
GPU根据厂商的不同,显卡型号的不同,GPU的架构也有差别,但是大体的设计基本相同,原理的部分也是相通的。下面我们就以NVIDIA的Fermi架构为蓝本,从降低延迟的角度,来讲解一下GPU到底是如何利用数据的并行处理来提升性能的。有关GPU的架构细节和逻辑管线的实现细节,我们将在下一篇里再讲。
无论是CPU还是GPU,都在使用各种各样的策略来避免停滞(stall)。
CPU的优化路线有很多,包括使用pipeline,提高主频,在芯片上集成访问速度更快的缓存,减少内存访问的延迟等等。在减少stalls的路上,CPU还采用了很多聪明的技术,比如分支预测,指令重排,寄存器重命名等等。
GPU则采用了另一种不同的策略:throughput。它提供了大量的专用处理器,由于GPU端数据的天然并行性,所以通过数据的大规模并行化处理,来降低延迟。这种设计优点是通过提高吞吐量,数据的整体处理时间减少,隐藏了处理的延迟。但是由于芯片上集成的核越多,留给其他设备的空间就越小,所以像memory cache和logical control这样的设备就会变少,导致每一路shader program的执行变得延迟很高。了解这个特性,我们来看一个例子,以此来说明如何利用GPU的架构,写出更高效的代码。
假如我们有一个mesh要被渲染,光栅化后生成了2000个fragment,那么我们需要调用一个pixel shader program 2000次,假如我们的GPU只有一个shader core(世界最弱鸡GPU),它开始执行第一个像素的shader program,执行一些算数指令,操作一下寄存器上的值,由于寄存器是本地的,所以此时并不会发生阻塞,但是当程序执行到某个纹理采样的操作时,由于纹理数据并不在程序的本地寄存器中,所以需要进行内存的读取操作,该操作可能要耗费几百甚至几千个时钟周期,所以会阻塞住当前处理器,等待读取的结果。如果真的只是这样设计这个GPU,那它真的就是太弱鸡了,所以为了让它稍微好点,我们需要提升它的性能,那如何降低它的延迟呢?我们给每个fragment提供一些本地存储和寄存器,用来保存该fragment的一些执行状态,这样我们就可以在当前fragment等待纹理数据时,切换到另一个fragment,开始执行它的shader program,当它遇到内存读取操作阻塞时,会再次切换,以此类推,直到2000个shader program都执行到这里。这时第一个fragment的颜色已经返回,可以继续往下执行了。使用这种方式,可以最大化的提高GPU的效率,虽然在单个像素来看,执行的延迟变高了,但是从2000个像素整体来说,执行的延迟减少了。
现代GPU当然不会弱鸡到只有一个shader core,但是它们也同样采用了这种方式来减低延迟。现代GPU为了提高数据的并行化,使用了SIMT(Single Instruction Multi Thread,SIMD的更高级版本),执行shader program的最小单位是thread,执行相同program的threads打包成组,NVIDIA称之为warp,AMD称之为wavefront。一个warp/wavefront在特定数量的GPU shader core上调度执行,warps调度器调度的基本单元就是warp/wavefront。
假如我们有2000个fragment需要执行shader program,以NVIDIA为例,它的GPU包含32个thread,所以要执行这些任务需要2000/32 = 62.5个warps,也就是说要分配63个warps,有一个只使用一半。
一个warp的执行过程跟单个GPU shader core的执行过程是类似的,32个像素的shader program对应的thread,会在32个GPU shader core上同时以lock-step的方式执行,当着色器程序遇到内存读取操作时,比如访问纹理(非常耗时),因为32个threads执行的是相同的程序,所以它们会同时遇到该操作,内存读取意味着该线程组将会阻塞(stall),全部等待内存读取的结果。为了降低延迟,GPU的warp调度器会将当前阻塞的warp换出,用另一组包含32个线程的warp来代替执行。换出操作跟单核的任务调度一样的快,因为在换入换出时,每个线程的数据都没有被触碰,每个线程都有它自己的寄存器,每个warp都负责记录它执行到了哪条指令。换入一个新的warp,不过是将GPU 的shader cores切换到另一组线程上继续执行,除此之外没有其他额外的开销。该过程如下图所示:
在我们这个简单的例子中,内存读取的延迟(latency)会导致warp被换出,在实际的应用中,可能更小的延迟就会导致warp的换出操作,因为换入换出的操作开销非常低。warp-swapping的策略是GPU隐藏延迟(latency)的主要方式。但是有几个关键因素,会影响到该策略的性能,比如说,如果我们只有很少的threads,也就是只能创建很少的warp,会使隐藏延迟出现问题。
shader program的结构是影响性能的主要角色,其中最大的一个影响因素就是每个thread需要的寄存器的数量。在上面例子的讲解过程中,我们一直假设例子中的2000个thread都是同时驻留在GPU中的。但是实际上,每个thread绑定的shader program中需要的寄存器越多,产生的threads就越少(因为寄存器的数量是固定的),能够驻留在GPU中的warp就越少。warps的短缺也就意味着无法使用warp-swapping的策略减缓延迟。warps在GPU中存在的数量称之为占用率,高占用率意味着有更多的warps可以用来执行,低占用率则会严重影响GPU的并行效率。
另一个影响GPU性能的因素就是动态分支(dynamic branching),主要是由if和循环引进的。因为一个warp中的所有线程在执行到if语句时,就会出现分裂,如果大家都是执行的相同的分支,那也没什么,但但凡有一个线程执行另一个分支,那么整个warp会把两个分支都执行一遍,然后每个线程扔掉它们各自不需要的结果,这种现象称为thread divergence。
了解了上面的基本原理,我们可以看出,整个GPU的设计其实也是一种trade-off,用单路数据的高延迟,来换整体数据的吞吐量,以此来最大化GPU的性能,降低stall。在实际的编码过程中,尤其是shader的编写过程中,也要严肃影响GPU优化策略的几个因素,只有这样,才能写出更加高效的代码,真正发挥出GPU的潜力。
下一篇会更加详细的介绍GPU的结构和逻辑管线,如果错误,欢迎指正。
- PyTorch使用并行GPU处理数据
- TensorFlow 多 GPU 处理并行数据
- 利用TESLA GPU和MATLAB实现大规模型数据并行处理
- 用于实时大数据处理的Lambda架构
- 【并行计算-CUDA开发】GPU 的硬体架构 <关于存储体的介绍比较好bank conflicts>
- GPU上并行处理大规模粒子系统
- 异步计算架构解决准实时数据处理
- 【2013Esri中国用户大会】ArcGIS的大数据挖掘和并行处理
- 关于Tomcat的点点滴滴(体系架构、处理http请求的过程、安装和配置、文件夹结构、设置压缩和对中文文件名称的支持、以及Catalina这个名字的由来……等)
- 全球最低功耗蓝牙单芯片DA14580的软件体系 -层次架构和BLE消息事件处理过程
- 大数据1-Hadoop架构体系及在各方面的应用
- 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台
- 数据仓库专题(24)-数据仓库架构体系总结
- 携程日处理20亿数据,实时用户行为服务系统架构实践
- 体积阴影GPU实现--数据预处理
- Lambda Architecture-大数据处理系统经典架构解析
- 分享一下三个写的比较好的tensorflow多GPU模型,实现数据并行/模型并行
- 电商网站是怎么处理大数据和并发架构网站
- 大数据处理架构Hadoop