缓存控制指令
在计算中,缓存控制指令是嵌入处理器指令流中的提示,旨在利用程序员或编译器提供的关于内存访问模式的资讯来提高硬件缓存的性能。 [1]它们可以通过更好地控制工作集来减少缓存污染、减少带宽需求、绕过延迟。大多数缓存控制指令不会影响程序的语义,尽管有些可以。
例子
此类指令受多个处理器指令集架构的支持,例如ARM、MIPS、PowerPC和x86。
预取
也称为数据缓存块触摸,其效果是请求加载与给定地址关联的缓存行。这是由x86指令集中的PREFETCH
指令执行的。一些变体绕过更高级别的缓存层次结构,这在“流式”上下文中对于遍历一次而不是保存在工作集中的数据很有用。预取应该发生得足够早,以减轻内存访问的延迟,例如在使用循环来线性遍历内存时。 GCC内建函数__builtin_prefetch
可用于在编程语言C或C++中调用。
指令预取
预取的一种变体,适用于指令缓存。
数据缓存块分配零
此提示用于在完全覆盖内容之前准备缓存行。在这个例子中,CPU 不需要从主内存加载任何东西。语义效果等同于使用对齐的memset,将缓存行大小的块设为零,但操作实际上是实现释放。
数据缓存块无效化
此提示用于丢弃缓存行,而不将其内容提交到主内存。该指令可能会带来不正确的运行结果,因此需要谨慎使用。与其他缓存提示不同,这条缓存提示会极大地改变程序语义。这与allocate zero
一起用于管理临时数据。这节省了不需要的主内存带宽,避免缓存污染。
数据缓存块刷新
此提示请求立即牺牲一个缓存行,为即将发生的分配让路。当已知数据不再是工作集的一部分时使用它。
其他提示
一些处理器支持加载-存储指令的变体,这些指令也包含缓存提示。一个例子是PowerPC指令集中的load last
,这表明数据只会被使用一次,即相应缓存行可能会被送到等待牺牲的队列的头部。
替代方案
自动预取
最近,英特尔和ARM开发的越发先进的应用处理器将更多的晶体管用于加速用传统语言编写的代码,例如执行自动预取、使用硬件来动态检测线性访问模式。因而缓存控制指令变得不那么流行了。然而,这些技术可能对面向吞吐量的处理器仍然有效,它们具有不同的吞吐量-延迟的侧重性,并且可能更倾向将更多区域用于执行单元。
暂存器内存
一些处理器支持将临时文件放入暂存器,并使用直接存储器存取(DMA)在需要时将数据传入传出主内存。 Cell 处理器和一些嵌入式系统使用这种方法。这些允许更好地控制内存流量和位置(因为工作集由显式传输管理),并消除了对多核机器中昂贵的缓存一致性的需求。
缺点是它需要使用截然不同的编程技术。很难改写用传统语言(如 C 和 C++)编写的程序,这些语言向程序员提供了一个大地址空间的统一视角(这是通过缓存模拟出来的错觉)。传统的微处理器可以更轻松地运行遗留代码,然后可以通过缓存控制指令对其进行加速,而基于暂存器的机器则需要从头开始进行专门编写,直至能实现相同功能。缓存控制指令特定于某个缓存行大小,在实践中,同一体系结构系列中的处理器各代之间可能不同。缓存还可以帮助从不可预测的访问模式(例如,在材质贴图的过程)中合并读取和写入,而暂存器 DMA 需要重新设计算法以实现更可预测的“线性”遍历。
此类暂存器通常更难与传统编程模型一起使用,不过对于资料流模型(例如TensorFlow)而言可能更合适。
向量读取
向量处理器(例如现代图形处理单元(GPU) 和Xeon Phi)使用大规模并行计算来实现高吞吐量,同时解决内存延迟问题(减少预取的需要)。许多读取操作是并行发出的,用于计算内核的后续调用;计算可能会暂停以等待后续的数据,而执行单元则尽力处理来自过去收到的请求的数据。对于程序员来说,这种模式更容易与适当的编程模型(内核函数)结合使用,但更难应用于通用编程。
缺点是临时状态的许多副本可能保存在处理元素的本地内存中,等待传输中的数据。
参考资料
- ^ Power PC manual, see 1.10.3 Cache Control Instructions (PDF). [2022-12-19]. (原始内容 (PDF)存档于2016-10-13).