Intel64及IA-32架构优化指南第7章——7.1 通用预取编码准则
第7章 优化Cache用法
在过去几十年,处理器速度已经增长。而存储器访问速度则以较慢的步伐增长。这所导致的不一致性使得将应用程序以两种方法之一进行调整而变得重要:要么(a)多数数据访问从处理器Cache来实行,要么(b)通过尽可能多地利用峰值存储器带宽来有效地屏蔽存储器延迟。
硬件预取机制在微架构中减轻后者[译者注:即上一段落中的(b)]方面是种增强,并且当与软件调整相结合时是最有效的。如果所需要的数据可以从处理器Cache来取或者如果存储器交通可以有效地利用硬件预取,那么大多数应用的性能会有相当大地提升。
将数据在处理器需要它之前带入的标准技术涉及到额外的编程,这可能对实现会有点难度并且需要额外的特殊步骤来防止性能下降。流化的SIMD扩展通过提供各种预取指令来定位这个问题。
流化的SIMD扩展引入了各种非临时的存储指令。SSE2扩展了这对新数据类型的支持并且也引入了对32位整型寄存器的非临时存储支持。
本章关注于:
● 硬件预取机制,软件预取以及与Cache相关的指令——讨论了微架构特征以及允许你在一个应用程序中影响数据cache的指令。
● 使用硬件预取、软件预取以及与Cache相关指令的存储器优化——讨论了使用上述指令来实现存储器优化的技术。
注:在一些所提供的样例中,所描述的预取和Cache利用是特定于当前对Intel NetBurst微架构的实现的,但对于后面的处理器来说大部分也适用。
● 使用确定性的Cache参数来管理Cache一致性。
7.1 通用预取编码准则
下列准则将帮助你减少存储器交通并更有效地利用峰值存储器系统带宽,当大量数据搬移必须源自存储器系统时:
● 利用硬件预取器的能力以线性模式来预取要访问的数据,要么以向前方向,要么以向后方向。
● 利用硬件预取器的能力以一个固定访问跨度的模式来预取要访问的数据,访问跨度大体上小于硬件预取的触发距离的一半(见表2-33)。
● 使用当前这一代的编译器,诸如支持C++语言层上SSE扩展特征的Intel C++编译器。SSE扩展和MMX技术指令提供了内建函数,允许你优化对Cache的利用。Intel编译器内建函数的例子包括:_mm_prefetch,_mm_stream以及_mm_load,_mm_sfence。详细地请参考Intel C++编译器用户指南文档。
● 通过以下方法来方便编译器优化:
——最小化对全局变量和指针的使用。
——最小化对复杂的控制流的使用。
——使用const限定符,避免register限定符。
——小心地选择数据类型(见下面)并避免类型投射。
● 使用Cache分块技术(比如,采用条带),如以下方式:
——通过使用Cache分块技术,诸如条带采用(一维数组)或循环分块(二维数组)来提升Cache命中率
——使用硬件预取机制来探测,如果你的数据访问模式具有足够的规律性来允许交替数据访问的顺序(比如:铺瓦)来提升空间位置。否则的话,使用PREFETCHNTA。
● 平衡单遍与多遍执行:
——单遍,或未分层的执行通过一整个计算流水线传一单个数据元素。
——多遍,或分层的执行在将整批数据传递到下一个阶段之前对一批数据元素执行一单个阶段的流水线。
——如果你的算法是单遍的,那么使用PREFETCHNTA。如果你的算法是多遍的,那么使用PREFETCHT0。
● 解决存储器段冲突问题。通过应用数组分组将所使用的数据连续地组合在一起或通过分配4KB以内的存储器页的数据来最小化存储器段冲突。
● 解决Cache管理问题。通过使用流化存储指令来最小化对保持在处理器Cache内的临时数据的干扰。
● 优化软件预取调度距离:
——向前足够远以允许临时的计算来叠交存储器访问时间。
——足够近所预取的数据不从数据Cache替换。
● 使用软件预取相关联的事务。安排预取以避免在一个内部循环的末尾不必要的预取并在下一个外部循环内预取内部循环的开头部分较少的迭代。
● 最小化软件预取次数。预取指令就总线周期、机器周期以及资源而言并不是完全毫无代价的;对预取过度的使用会相反地影响应用性能。
● 用计算指令交叉预取。为了获得最佳性能,软件预取指令必须在指令顺序中用计算指令来散布(而不是集中在一起)。