112

如何用Cache在C++程序中进行系统级优化(一)? - 恒生技术之眼 - 恒生研究院

 6 years ago
source link: http://rdc.hundsun.com/portal/article/838.html?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何用Cache在C++程序中进行系统级优化(一)?

软件在架构优化、算法优化、代码优化之后,往往会进入系统级优化,比如Cache优化。本文主要介绍 Cache的相关知识,然后列举一些常见的优化措施。

Cache就是缓存,一般用在慢速设备和快速设备之间,目的是方便快速存取。比如无处不在的自动售卖机,能让我们不用去超市,就能买到需要的食物和饮料,所以它可以看成是超市的Cache,商家会把人们最常用的商品,放入其中,并会根据售卖情况更新商品。

现代CPU中的处理器和内存之间,存在着巨大的速度差异,处理器访问内存数据一般要花费几百个时钟周期,如果需要的数据没有就位,指令就不能执行,处理器只能等待,Cache就是用来匹配处理器和内存的速度差异。CPU通过多级流水线、乱序执行、分支预测和预取技术,提前将数据从内存载入到Cache中,在需要的时候,直接从Cache中获取,因为Cache的速度更快,这样就提高了CPU的处理能力。

现代CPU的Cache一般分为三级:

一级,Cache生产成本最高,容量最小,但是速度最快,处理器访问一级Cache中的数据一般只需要3~5个指令周期,一级Cache又分为数据Cache和指令Cache,分别缓存数据和指令;

二级,Cache不区分数据和指令,容量较大,速度较慢;

三级,Cache也称LLC(Last Level Cache),容量最大,而速度更慢。

在多核时代,一般一颗CPU中,每核都有独立的一级和二级Cache,而多核之间共享三级Cache。如下图:

f_dc2a4a8d420a32c255dd8a5d0b57c004.png
【Cache读写机制】

Cache和内存之间按块进行数据交换,块大小为内存的一个存储周期能访问到的数据长度,一般为64字节,又称为Cache Line或缓存行。Cache和内存的关联方式以及Cache回写策略等,对于上层软件优化来说,可以暂不考虑。

Cache预取】

Cache之所以能提高系统性能,主要在于程序执行具有局部性现象,包括时间局部性和空间局部性。

时间局部性是指,程序即将用到的数据和指令可能就是目前正在使用的数据和指令。利用时间局部性,可以将当前访问的数据和指令存放到Cache中,以便将来使用,比如C++语言中的for、while循环、递归调用等。空间局部性是指,程序即将用到的数据和指令可能与目前正在使用的数据和指令在地址空间上相邻或者相近。

利用空间局部性,可以在处理器处理当前数据和指令时,把内存中相邻区域的指令/数据读取到Cache中,以备将来使用,比如数组访问、顺序执行的指令等。局部性原理也符合80-20原则,即程序20%的代码占用了处理器百分之八十的执行时间,占用了80%的内存。Cache预取就是根据局部性原理,预测数据和指令使用情况,并提前载入到Cache中,这样,当数据/指令需要被使用时,就能快速从Cache中获取到,而不需要访问内存。

【Cache一致性】

现代CPU基本都是多核架构,多个核心有各自的L1/L2 Cache,当多个核心需要修改同一CacheLine时,需要在多核之间进行Cache同步,以保证Cache 一致性,Intel CPU使用MESIF协议来实现Cache一致。

总结一下,Cache主要特点有:
■ 容量小、速度快,可以使用下面命令在Linux上查询Cache容量:

f_1e7f31dd7d36c9278ed907d815964216.png

■ 按行存取:一般缓存行大小为64B,可以使用下面命令在Linux上查询Cache Line大小: 

f_fcd0b006516a1b04ec6e815930ac9546.png

■ 硬件预取:根据程序执行的局部性原理,CPU中的预取部件会对Cache进行预取,当前正在访问的数据/指令,以及周围区域的数据/指令都会被提前放入Cache。
■ 缓存一致性:多核同时修改同一行Cache时,需进行Cache同步。

■ 同时缓存指令和数据。

■ TLB:CPU中还有一种Cache叫做TLB(Translation Lookaside Buffer),专门用来缓存虚拟地址到物理地址的映射,加快地址解析。

在开发频繁访问内存的应用时,Cache对应用的性能影响很大,这就要求我们尽量编写Cache友好型代码,下面列举一些使用经验:

CPU 绑定

当进程被调度到其他核心或其他CPU后,Cache中的指令和数据都将无效,TLB缓存也将失效。在极速场景下,需要避免这种情况,可以通过设置CPU的线程亲缘性来减轻,也称为CPU绑定。还可以进一步通过配置isolcpus隔离CPU,降低CPU被调度的概率。需要注意的是,一般情况下不推荐绑定CPU。

f_c1667e2c49b9e4f21d5cd9da98b07b4c.png

上面代码中,将当前线程绑定到了xxx编号的CPU上。

系统调用
在系统调用时,将导致用户态和内核态切换,不仅整个切换过程耗时,而且由于用户态和内核态执行的代码不一样,会导致Cache失效。可以通过减少或者合并系统调用,来降低影响。比如写文件系统调用为write(),同时系统也提供了writev(),实现一次写入多块数据,还提供了文件映射内存mmap(),该方式可以基本避免系统调用。

本篇内容主要介绍了Cache的基础知识和在开发应用时,列举一些Cache在实际优化系统时的使用经验,下文我们会继续以实战的模式介绍如何通过Cache进行系统级优化。敬请关注!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK