6.3.1. 硬件预取
CPU 启动硬件预取的触发,通常是二或多个cache错失的某种模式的序列。这些cache错失可能在cache行之前或之后。在旧的实作中,只有邻近cache行的cache错失会被识别出来。使用当代硬件,步伐也会被识别出来,代表跳过固定数量的cache行会被识别为一种模式并被适当地处理。
若每次单一的cache错失都会触发一次硬件预取,对于效能来说大概很糟。随机memory存取模式 –– 例如存取全域变数 –– 是非常常见的,而产生的预取会大大地浪费 FSB 频宽。这即是为何启动预取需要至少两次cache错失。处理器现今全都预期有多于一条memory存取的串流。处理器试著自动将每个cache错失指派给这样的一条串流,并且在达到门槛时启动硬件预取。CPU 现今能追踪更高阶cache的八到十六条单独的串流。
负责模式识别的单元与各自的cache相关联。可以有一个 L1d 与 L1i cache的预取单元。很可能有一个 L2 与更高阶cache的预取单元。L2 与更高阶cache的预取单元是被所有使用相同cache的其它处理器核与 HT 所共享。八到十六条单独串流预取单元的数量便因而迅速减少。
预取有个大弱点:它无法跨越分页边界。理解到 CPU 支援需求分页(demand paging)时,原因应该很明显。若是预取被允许横跨分页边界,存取可能会触发一个事件,以令分页能够被取得。这本身可能很糟,尤其是对效能而言。更糟的是预取器并不知道程序或操作系统本身的语义(semantic)。它可能因此预取实际上永远不会被请求的分页。如此意味著预取器会运行超过处理器曾以可识别模式存取过的memory区域尽头。这不只可能,而且非常有可能。若是处理器 –– 作为一次预取的一个副作用 –– 触发对这样的分页的请求,操作系统甚至可能会在这种请求永远也不会发生时完全扔掉它的追踪纪录。
因此重要的是认识到,无论预取器在预测模式上有多厉害,程序也会在分页边界上历经cache错失,除非它明确地从新的分页预取或是读取。这是如 6.2 节描述的最佳化资料布局、以借由将不相关的资料排除在外来最小化cache污染的另一个理由。
由于这个分页限制,处理器现今并没有非常复杂的逻辑来识别预取模式。以仍占主导地位的 4k 分页大小而言,有意义的也就这么多。这些年来已经提高识别步伐的地址范围,但超过现今经常使用的 512 位元组窗格(window)可能没太大意义。目前的预取单元并不认得非线性的存取模式。这种模式较有可能是真的随机、或者至少足够不重复到令试著识别它们不具意义。
若是硬件预取被意外地触发,能做的只有这么多。一个可能是试著找出这个问题,并稍微改变资料与/或程序布局。这大概满困难的。可能有特殊的在地化(localized)解法,像是在 x86 与 x86-64 处理器上使用 ud2 指令35。这个无法自己执行的指令是在一条间接的跳跃指令后被使用;它被作为指令获取器(fetcher)的一个信号使用,表示处理器不应浪费精力解码接下来的memory,因为执行将会在一个不同的位置继续。不过,这是个非常特殊的情况。在大部分情况下,必须要忍受这个问题。
能够完全或部分地停用整个处理器的硬件预取。在 Intel 处理器上,一个特定模型暂存器(Model Specific Register,MSR)便用于此(IA32_MISC_ENABLE,在许多处理器上为位元 9;位元 19 只停用邻近cache行预取)。这在大多情况下必须发生在系统核心中,因为它是个特权操作。若是数据分析显示,执行于系统上的一个重要的应用程序因硬件cache而遭受频宽耗竭与过早的cache逐出,使用这个 MSR 是一种可能性。
35. 或是 non-instruction。这是推荐的未定义操作码。 ↩