您的位置:首页 > 其它

组成:GPU与CPU的比较

2016-12-31 14:02 197 查看

GPU与CPU的比较

不管是CPU还是GPU都是很高速的设备,然而从主存中取数据会很慢很慢,为了充分利用高速设备,二者都使用了缓存,编程一定要考虑的是计算机程序的局部性(locality)的基本属性—–时间局部性与空间局部性。多核时代和GPU并行运算时代来临,另外一个影响程序运行的速度的关键因素—–缓存一致性(Cache Coherency)。这些CPU和GPU都有一些区别,深入底层才能写出更高效的代码!

我觉得在2017年仍然有必要学习CUDA。虽然各路大腿子都用自家编的或者优化的编译器,很多还都是不开源的,但是对我来说使用CUDA仍然是最简单的方式去提升程序性能。代码编的好的话至少比CPU快吧,而且CPU代码想写好了必须汇编优化,这个事干起来比较蛋疼。

CPU

局部性

[b]时间局部性:[/b]

由于时间局部性,同一数据对象可能会被多次使用,一旦一个数据对象在第一次不命中时被拷贝到缓存中,我们就会期望后面对该目标有一系列的访问命中。因为缓存比低一层的存储设备更快,对后面的命中服务会比最开始的不命中快很多。

[b]空间局部性[/b]

块通常包含多个数据对象。由于空间局部性,我们会期望后面对该块中的其他对象的访问能够补偿不命中后拷贝该块的花费。

存储器层次结构

为了让CPU不把时间都耗在读主存上,现代计算机搞了一个三级缓存,L1,L2挨着CPU,L3挨着内存,这仨都是SRAM,所以又小又快又贵。他们全部由硬件管理,所以相对不是很灵活。

L1高速缓存的速度几乎和寄存器一样快,典型的是2~4个时钟周期。

L2大约10个时钟周期能访问到

L3需要30~40个时钟周期

Cache对用户是透明的,很多时候只能猜。C语言中有一个register修饰符,但是也仅仅是对编译器的建议,编译器不一定采用。组成原理会讲直接映射,组相联,全相联啊之类的东西。这个东西比较迷,我并不知道我以后的电脑用的哪种方式。但是会影响程序性能的就是缓存不命中,如果能像做题一样算出来一段程序的缓存命中率就好了。但是不好。那就猜一下。一般来说Intel Core i7 用的是组相连,而且L1和L2是8路组相连,L3是16路组相连。

很致命的应该是抖动(thrash)。也就是Cache反复的加载和驱逐相同的高速缓存组.

其他的大致来说有三点

1.Cache大小&块大小:大的能提高空间局部性的利用,提高命中率却增加命中时间,损害了时间局部性。

2.相联度:高相联度会降低抖动的可能性,但是增加命中时间,不命中处罚,而且贵。

3.写策略

读比较简单,命中就返回CPU,不命中就讲块读到Cache里然后返回给CPU。写的话要一层一层地来,写了数据就会脏掉,脏了就会很麻烦。

写策略比较复杂,CPU和GPU有区别,单核CPU和多核CPU也有区别,这里先说最简单的单核CPU。

现在来讲简化的模型是写回和写分配(write-back and write-allocate), 比较适合现在的高速CPU。

写回

尽可能推迟存储器更新,只有当替换算法要驱逐更新过的块时,才把他写到紧接着的低一层。由于局部性,写会能显著减少总线流量。

写分配

当写不命中的时候,加载相应的低一层块到Cache中,然后更新这个Cache块。写分配试图利用写的空间局部性,但是缺点是每次不命中都会导致一个块从低一层传送到高速缓存。

写回写分配和读是对称的,而且实际上用的比较多

因为缓存和缓存替换机制都是基于局部性设计的,所以如果能尽量运用局部性就可以一定程度上提升程序性能。

以上都是从理论上来说的。

从实践来讲,主要考察循环的使用,特别是循环访问多维数组的时候。

1.对局部变量的反复引用是好的

2.步长为1的引用模式是好的

3.循环的排列很重要!

1和2也就是时间局部性和空间局部性。第三点更细

比如说一个三重循环,有

ijk,jik,jki,kji,kij,ikj六种排列方式,他们每一个的效率都是不一样的。

具体怎么样要具体分析。但是在C语言中,数组是按行顺序存储的,所以访问应该尽量按行访问。也就是行>列

比较炫酷的是直接在程序里把数据分块,差不多每次都只对整体的一小块在Cache上操作,但是这样代码会很难读。

多核CPU与缓存一致性

还是写问题。多核CPU的写问题就比较迷了。

现在我们有多个核,每个核又都有自己的Cache和公有的Cache。

依然拿Intel Core i7举例:

主存:共享

L3:共享

L2:一人一块

L1:一人两块(放数据的和放指令的)

于是问题来了:如果某个CPU缓存段中对应的内存内容被另外一个CPU偷偷改了,那就很尴尬了,特别是使用上文提到的写回模式。

为了解决这个问题,就有了缓存一致性协议,以保持缓存的完全一致,但这种协议都是有代价的,都是各种限制回写的规则,使得多核CPU不能发挥1+1=2的性能。代表性的例子是MESI协议,戳这里详细了解

缓存一致性(Cache Coherency)入门

一个内存的写需要通知所有核的各个级别的缓存,无论何时,所有处理器的核看到的内存视图都完全一样。随着处理器核的数量增多,通知的开销迅速增加,核心的数量被缓存一致性制约了。

千核并行的GPU

手头的教材是2014年的。GPU发展太快了,所以我并不是很清楚现在的是什么样子的。

但还是先说一下现在的吧。深度学习太吃GPU了,学校用的是Titan X,晶体管数目高达12,000,000,000,拥有3584个CUDA单元。毕竟核弹厂。。

好了不扯其他的回来说组成

局部性

GPU与CPU一个很重要的区别就是他的局部性。

CPU的局部性是由硬件管理的,程序员只能尽量写出缓存友好的代码。

而GPU中的局部性是由程序员负责的,程序员在需要数据前就把他们装入Cache。Cache中最麻烦的还是那些被写过的脏数据。由程序员控制的Cache写,我们就可以把一直在进行的局部变换变量们放在Cache里并且只在最后写回全局内存。这就灵活很多啦。但是程序其实很多不是很好并行的,也不是每个程序都有很好的局部性,这些就自己权衡啦。

组成

[b]总线[/b]

普通电脑的GPU是挂在PCI-E3.0上的。PCI-E用于沟通CPU-GPU,GPU-GPU就显得很bottleneck 了,10GB/s吞吐量完全不够看,N厂很多卡现在都是nvlink的,这就很炫酷了。


NVLink则提供了一个高效率而又高带宽的链接路径,可以使GPU与CPU的存储系统之间有5~12倍于PCI-E 3.0的带宽,也就是说带宽能够达到80GB/s~近200GB/s。如果您的CPU不支持NVLink,这个总线仍然可以互连GPU与GPU。NVLink的能效可以达到PCI-E 3.0的两倍以上。

但是根据我的理解和实践经验,这件事并没有这么简单。首先是学校的机子。

tensorflow跑CIFAR10的时候用了四张卡。但是开头会有报告GPU的使用情况。

学校的GPU是0和1互联,2和3互联。感觉上互联应该就是nvlink,不互联就是PCI-E。所以我之前的玩具代码使用率才20–40%应该是数据走PCI-E太多了,并且经常往返于CPU和GPU。最后计时我的笔记本和4Titan一样了

群里的大神说:一定要避免一个流程反复进入GPU,就是避免在每个step循环内部多次sess.run的情况(tensorflow)

另外听说天价微型超算DGX-1也是只能三卡互联,想要八卡并行的话比较好的解决方案是通过一张GPU中转,而不是走CPU。不过这个跟我没什么关系,就瞎说。

[b]GPU硬件结构[/b]

sp: 最基本的处理单元,streaming processor 最后具体的指令和任务都是在sp上处理的。GPU进行并行计算,也就是很多个sp同时做处理

sm:多个sp加上其他的一些资源组成一个sm, streaming multiprocessor. 其他资源也就是存储资源,共享内存,寄储器等。

warp:GPU执行程序时的调度单位,目前cuda的warp的大小为32,同在一个warp的线程,以不同数据资源执行相同的指令。

grid、block、thread:在利用cuda进行编程时,一个grid分为多个block,而一个block分为多个thread.其中任务划分到是否影响最后的执行效果。划分的依据是任务特性和GPU本身的硬件特性。

[b]缓存[/b]

GPU是非缓存一致系统的,他不会自动更新其他核的缓存,需要由程序员写清楚每个处理器的目标区域

一般两级缓存。

sm下方的是L1 Cache

texture memory:针对global memory的一个特殊视图,用来存储差值计算所需的数据,拥有基于硬件进行插值的特性。

constant memory:存储只读数据,所有GPU卡均对其缓存,也是global memory的一个视图

L1下方就是L2 Cache

shared memory:这个是程序员可控的Cache,与CPU不同,他没有自动完成数据替换的硬件逻辑,而是完全由程序员控制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cuda gpu cpu