我试图了解指令缓存的工作原理。
执行代码块时会预取多少额外的缓存行?它是否考虑了分支预测?
如果一个代码块包含一个函数调用,那么函数代码体是按顺序加载还是在缓存的不同部分加载?
例如,以下代码片段是否相同?
if (condition) {
// block of code that handles condition
}
和
if (condition) {
handle_condition(); // function that handles the condition
}
哪一个减少"漏洞"如果条件很少是真的,在指令序列中?
if
条件永远不会成立的代码的一部分,那么if
条件的主体是否最终会被驱逐,而将代码体的其余部分保留为-is?我假设这些问题没有依赖于特定微架构的答案。但如果他们这样做,我有一个x86-64 Intel Sandy Bridge。
答案 0 :(得分:3)
实际上,答案非常依赖于微观架构。这不是由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个完全关联的条目。
换句话说,并且如图所示,分支预测是管道的第一步,并且在指令高速缓存访问之前。因此,高速缓存将保持由分支预测器预测的地址“跟踪”。如果几乎没有访问某个代码片段,预测器将避免它,并且它会随着时间的推移而从I-cache中老化。由于分支预测器应该能够处理函数调用,因此代码片段之间不应存在根本区别。
这当然因对齐问题而中断(I-cache有64B行,你不能在那里有部分数据,所以内联代码实际上可能会导致比函数调用更多无用的开销,尽管两者都是有界的),并且由于当然是错误的预测。 其他HW-prefetchers也可能正在工作并可能将数据提取到其他级别,但这并不是官方公开的内容(这些指南只提到了一些L2缓存预取,可能有助于减少延迟而不会破坏你的L1缓存)。另请注意,Sandy bridge有一个uop缓存,可能会增加进一步的缓存(但也更复杂)。