前言
预取是在处理器实际需要之前,将指令或数据从较慢的内存中提取到较快的cache中,来最大程度地减少cache未命中的延迟。预取器通常能预测下一个使用的缓存行,将他们载入缓存,但是也容易出错导致缓存污染,并给内存子系统带来额外的压力。在高性能处理器中,处理高速缓存未命中或延迟以及正确管理内存带宽,预取是比较有用的方法。在分布式共享内存(DSM)系统中,远程内存访问比本地访问花费更长的时间,因此数据预取对于此类系统非常有效。
预取分为硬件预取和软件预取:基于硬件的预取通常是通过在处理器中具有专用的硬件机制来完成的,该机制监视正在执行的程序所请求的指令或数据流,基于该流识别程序可能需要的数据,然后预取到处理器的缓存中;基于软件的预取通常是通过让编译器分析代码并在编译期间在程序中插入预取指令来完成。
-
硬件预取:流缓冲区是使用中最常见的基于硬件的预取技术之一,该技术最初由 Norman Jouppi提出,它将cache miss的地址提取到单独的深度缓冲区中,与缓存分开。如果预取的块关联的地址与在处理器上执行的程序生成的请求地址匹配,则处理器从流缓冲区中读取数据/指令,如下图所示:
-
软件预取:编译器指导的预取被广泛用于具有大量迭代的循环中,这些预取是非阻塞的内存操作,即这些内存访问不会干扰实际的内存访问,它们不会更改处理器的状态或引起页面错误。软件预取的一个主要优点是,它减少了强制性高速缓存未命中的次数。
硬件预取有较少的CPU开销,且需要程序的实时运行信息以估计预取对象,不可人为控制,而软件预取更方便处理,性能也依赖编译器或源码。
以上硬件、软件预取介绍来自Wiki,更多关于预取的说明请参见:https://en.wikipedia.org/wiki/Cache_prefetching
GCC中的预取
GCC主要在tree-ssa上对循环数组进行预取优化操作,GCC8.2.0中预取相关的选项有:-fprefetch-loop-arrays、prefetch-latency、simultaneous-prefetches、min-insn-to-prefetch-ratio、prefetch-min-insn-to-mem-ratio
。其中-fprefetch-loop-arrays -faggressive-loop-optimizations
选项对benchmark性能有较大影响,尤其是619.lbm_s
。
- fprefetch-loop-arrays:如果目标计算机支持,则生成指令以预取内存,提高访问大型数组循环的性能。此选项可能会生成更好或更差的代码,很大程度上取决于源代码中循环的结构。这是主要的预取选项。
- prefetch-latency:估计预取完成之前执行的平均指令数。预先预取的距离与该常数成比例,增加此数目可能减少预取。
- simultaneous-prefetches:可以同时运行的最大预取数。
- min-insn-to-prefetch-ratio:允许在循环中进行预取的指令数量和预取数量之间的最小比。
- prefetch-min-insn-to-mem-ratio:允许在循环中进行预取的指令数与内存引用数之间的最小比。
GCC中数据的预取分为读预取和写预取。预取受空间局部性和时间局部性影响,属于局部性优化,预取的三个因素为:预取数量、预取距离和预取类型,有效的预取距离起着至关重要的作用。
gcc-8.2.0中预取相关的主要源码在tree-ssa-loop-prefetch.c
中,由pass_loop_prefetch
描述,该pass的源码定义如下:
namespace {
const pass_data pass_data_loop_prefetch =
{
GIMPLE_PASS, /* type */
"aprefetch", /* name */
OPTGROUP_LOOP,