【问题标题】:Instruction cache loading and eviction rules指令缓存加载和逐出规则
【发布时间】:2015-06-16 16:09:50
【问题描述】:

我正在尝试了解指令缓存的工作原理。

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

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

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

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

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

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

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

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

【问题讨论】:

  • 现代分析编译器肯定会尝试将热路径打包在尽可能少的缓存块中。
  • 我建议您阅读有关函数内联的内容。
  • @EOF - 我说的是函数没有内联的情况。
  • @BenVoigt - 在这种情况下,编译器不会知道该块是否是热路径的一部分,对吧?
  • 您可以使用gccclang 扩展来标记unlikely 等条件。查看此帖子:stackoverflow.com/questions/10922607/…

标签: c performance assembly cpu-cache micro-architecture


【解决方案1】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2016-08-02
    • 2013-01-20
    • 1970-01-01
    • 1970-01-01
    • 2018-02-10
    • 2018-01-13
    • 1970-01-01
    • 2015-09-14
    相关资源
    最近更新 更多