GPGPU简介

“Two strong oxen or 1024 chickens?”

之前CPU系列的内容:

大家应该能感觉到,近些年来,CPU的发展速度远远跟不上GPU的发展速度,这里有很多因素,比如AMD的疲软,Intel主观上缺少动力,比如GPU更适合大计算量的应用,因此CPU没有太多必要提升计算能力。总之,一切都是由市场这个看不见的手来操纵。如下图,相比CPU,GPU计算能力更强,价格也更便宜。

GPU架构

我们和CPU做一个简单的类比:

  • SM(shading multiprocessors)->CPU Cores
  • Warps->hyperthreading
  • 每个warp包含32个threads,相当于SIMD
  • 每个warp内的线程执行相同的指令
  • 每个SM中有多个register,可以在warps间共享
  • Sharedmem->L1 Cache
  • Global memory->内存

和CPU之间不同的是,GPU的内存是可编程的,而CPU的缓存是不可编程的;GPU的线程管理是不可编程的,而CPU的多线程管理(SIMD)是不可编程的。

这里有两点强调,第一,CPU通过Cache来解决latency,而GPU则通过并行来解决这个问题,比如原本有5s延迟,因为缓存优化,只有1s延迟,而GPU并没有缓存,所以还是有5s延迟,但因为有1000个线程同时并行,所以整体的latency就降低了。第二,就是GPU的thread scheduler,如下图,解释了为什么GPU中尽可能减少逻辑判断

GPGPU编程

目前,我所了解的主要有三种,Compute Shader,CUDA和OpenCL,这个是个人的优先级。在编程角度,思想上都大同小异。

首先是Global Size和LocalSize的理解,首先,我们需要计算的数据有可能是一维,二维甚至N维,对应global和local的维度。下图是一个二维数据(比如图片)对应的概念:

这样,我们便可以获取每一个thread对应的相对位置和全局位置,实现业务需求,如下,global(800,400),local(32,32),column line和getlocal id则是对应的索引。

其次,作为运算的参数和结果,我们尽可能减少内存和显存之间的转换,比如我们计算创建一张纹理(GPU),getBits(RAM),然后OpenGL渲染(GPU),在这种场景下,如果在GPGPU中的纹理能够直接对应OpenGL的Texture,则可以直接渲染,省去了FromDevice和ToDevice的操作,性能会有很大的提高。OpenCL和CUDA都支持绑定Texture对象,而Compute Shader自动支持。

整体来说,OpenCL需要自己做一个简单封装,方便调用,ComputeShader需要我们对OpenGL有不错的理解,CUDA可以通过VS自动创建,更为易用。下图是我使用三种框架做的一个Voronoi noise,github: https://github.com/pasu/opengllearner

GPGPU的应用

首先,大规模的计算,比如CNN神经网络或者挖矿,这类应用最适合GPU,没有太多技术难点,就是怕GPU闲着,堪称GPU的996。

其次,很多CPU时代的算法并不支持并行,比如排序,如何能够实现GPU版本的算法(Bitonic sort),需要我们设计新的轮子了。

比如下面这个Prefix sum的并行版本,原本存在的loop dependency O(N),在并行版本下为O(logN)的loop,而每一个loop内部则是完全的并行计算(蓝色箭头示意部分)。

最后,还有一些计算量大,逻辑也很复杂的应用。比如GPU Ray Tracing就是一个很大,也很诱人的领域,can you feel it?

回到开头问题,当我们锄地时,你会选择两头牛呢,还是1024只鸡,希望这篇文章能帮助你找到自己的答案~

原文发布于微信公众号 - LET(LET0-0)

原文发表时间:2019-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券