2

我试图了解指令缓存是如何工作的。

  • 执行代码块时预取了多少额外的缓存线?它是否考虑到分支预测?

  • 如果一个代码块包含一个函数调用,函数代码体是按顺序加载还是在缓存的不同部分加载?

例如,以下代码片段是否相同?

 if (condition) {
   // block of code that handles condition
}

if (condition) {
    handle_condition(); // function that handles the condition
}

如果条件很少为真,哪一个会减少指令序列中的“漏洞”?

  • 如果我的第一个示例是经常运行的代码的一部分并且if条件永远不会为真,那么if条件的主体最终会被驱逐,而代码主体的其余部分保持原样吗?

我假设这些问题没有取决于特定微架构的答案。但万一他们这样做了,我有一个 x86-64 Intel Sandy Bridge。

4

1 回答 1

3

实际上,答案很大程度上取决于微架构。这不是 x86 或任何其他架构定义的东西,而是留给设计人员在各个世代中实现和改进。

对于 Sandybridge,您可以在这里找到一个有趣的描述 。最相关的部分是 -

上面的图 2 显示了 Sandy Bridge 的指令提取。分支预测在指令提取之前稍微排队,因此通常隐藏已执行分支的停顿,这是早期在 Merom 和 Nehalem 中使用的功能。对 32B 指令进行预测,而指令一次从 L1 指令高速缓存中提取 16B。

一旦知道下一个地址,Sandy Bridge 将同时探测 uop 缓存(我们将在下一页讨论)和 L1 指令缓存。L1 指令缓存为 32KB,有 64B 行,关联性增加到 8 路,这意味着它是虚拟索引和物理标记的。L1 ITLB 在小页面的线程之间进行分区,每个线程都有专用的大页面。Sandy Bridge 为大页面添加了 2 个条目,使 4KB 页面(两个线程)的总数达到 128 个,大页面(每个线程)有 16 个完全关联的条目。

换句话说,如图所示,分支预测是管道的第一步,在指令缓存访问之前。因此,缓存将保存由分支预测器预测的地址“跟踪”。如果某个代码片段很难被访问,预测器将避免它,并且随着时间的推移它会从 I-cache 中老化。由于分支预测器应该能够处理函数调用,因此您的代码片段之间应该没有根本区别。

这当然会由于对齐问题而中断(I-cache 有 64B 行,你不能在那里有部分数据,因此内联代码实际上可能比函数调用造成更多无用的开销,尽管两者都是有界的),并且由于 false当然是预测。也有可能其他硬件预取器正在工作并且可能会将数据提取到其他级别,但这不是官方披露的(指南只提到了一些 L2 缓存预取,这可能有助于减少延迟而不会破坏 L1 缓存)。另请注意,Sandy 桥有一个 uop 缓存,可能会增加更多缓存(但也更复杂)。

于 2015-04-12T22:44:39.973 回答