问题描述
我还没有找到明确的答案:是控制单元本身获取预定义的指令来执行缓存驱逐,还是操作系统进行干预?如果是这样,如何?
解决方法
计算机的哪个部分管理缓存替换?
通常;缓存管理缓存替换本身(它不是由单独的部分完成的)。
缓存有很多种,有些是通过软件实现的(DNS 缓存、网页缓存、文件数据缓存),有些是通过硬件实现的(指令缓存、数据缓存、翻译后备缓存)。
适用于所有情况;每当需要将新数据插入缓存而没有足够空间时,就需要快速驱逐其他数据,为新数据腾出空间。理想情况下,“最不可能很快需要”数据应该被驱逐,但这太难确定了,所以大多数缓存做出(可能不正确的)假设“最近最少使用”是“最不可能很快需要”的一个很好的预测指标。
通常这意味着将某种“上次使用时间”与数据一起存储(对于缓存中的每个项目);这意味着(为了性能)通常“最近最少使用”(和驱逐本身)直接内置到缓存的设计中(例如“上次使用时间”信息与其他元数据一起存储在“缓存标签”中).
,硬件缓存管理自己的替换,通常使用 pseudo-LRU 方法来选择要逐出集合的哪种方式。(真正的 LRU 需要太多位用于状态,尤其是对于 8-方式或更多关联。)另见 http://blog.stuffedcow.net/2013/01/ivb-cache-replacement/ - 大型慢速缓存(如现代 Intel CPU 中的 L3 缓存)可能会使用自适应替换策略来尝试保留一些有价值的行,即使有大量缓存未命中的巨大缓存没有太多未来价值的工作集。
如果我们考虑一个操作系统参与管理硬件缓存会是什么样子,我们很快就会发现仅仅实现它是多么疯狂(处理程序可以访问内存吗?如果它需要替换怎么办?一组中的一行?)而这种性能将是一场灾难,以及实现的复杂性。从这个推理中,我们可以看到为什么在同一个缓存检查和更新硬件中内置了专用逻辑门。
在每次缓存未命中时捕获到操作系统会使缓存未命中成本更高。 有些会触发很多的缓存替换,例如循环大型数组,其中大多数访问至少在一级缓存中未命中(如果您没有为硬件预取进行足够的计算以保持领先)。它还会损害内存级并行性(同时进行多个缓存未命中),这对于隐藏大内存延迟非常重要。我想如果您只是选择要驱逐的行,处理程序可以返回而无需实际等待缓存未命中本身解决,因此您可以在另一个缓存未命中仍在进行中时再次运行它。但是内存排序规则会使这变得很粗略:例如,一些 ISA 保证加载似乎是按程序顺序发生的。
捕获到操作系统的处理程序会刷新大多数普通 CPU 上的管道。
此外,硬件预取:硬件能够推测提前读取负载流当前正在读取的位置很重要。这样,当实际需求负载发生时,它有望命中 L2 甚至 L1d 缓存。 (如果真实缓存中的替换必须由操作系统管理,那么您需要一些单独的预取缓冲区,操作系统可以从中读取?如果您希望预取工作,那么复杂的程度是疯狂的,但为了正确性,这是必要的) .
此外,操作系统会做什么?运行加载数据的指令以确定要替换哪一行?如果这些加载/存储造成更多缓存未命中怎么办。
此外:存储在之后不会真正提交到 L1d 缓存,直到它们从 OoO exec CPU 中的乱序后端退出。也就是说,直到他们被认为是非投机性的。 (存储缓冲区允许这种解耦)。此时没有办法回滚它们;他们肯定需要发生。如果您在检测到第一个缓存未命中之前(或同步发生缓存未命中加载时)在存储缓冲区中有多个缓存未命中存储,那么假设的缓存未命中异常处理程序如何在不违反内存模型的情况下执行任何操作(如果需要)商店订购。这似乎是一场噩梦。
我一直假设“缓存未命中处理程序”类似于软件 TLB 未命中处理程序(例如,在 MIPS 或其他不执行硬件页面遍历的 ISA 上)。 (在 MIPS 中,TLB 未命中异常处理程序必须在具有固定转换的特殊区域中使用内存,以便可以在不导致更多 TLB 未命中的情况下访问。)唯一有意义的是操作系统提供某种类型执行替换策略的“微码”,当需要替换时,CPU 内部运行它,而不是与主 CPU 正常执行指令的顺序。
但实际上可编程微码效率太低了;它没有时间检查内存或任何东西(除非有保留供此微码使用的持久缓存速度状态)。 专用硬件可以在一两个时钟周期内做出决定,逻辑直接连接到该缓存的状态位。
选择提供和跟踪的状态与替换算法的选择密切相关。因此,只有在有更多选择或大量状态时,可编程才有意义。
LRU 需要在缓存命中时更新状态跟踪。 捕获到操作系统以让它选择如何在每次缓存命中时更新内容对于可接受的性能显然是不合理的;每次内存访问都会陷入陷阱。