repz ret:为什么这么麻烦?

repz ret: why all the hassle?

repz ret 的问题已在此处 [1] as well as in other sources [2, 3] 得到相当满意的解决。但是,我没有阅读这些来源,而是找到了以下问题的答案:

  1. 在与retnop; ret的定量比较中,实际惩罚是多少?特别是在后一种情况下——当大多数函数要么有 100 多条指令,要么被内联时,解码一条额外的指令(还有一条空指令!)真的很重要吗?

  2. 为什么这个问题在 AMD K8 中一直没有得到修复,甚至在 K10 中也出现了?既然什么时候记录一个丑陋的变通方法是基于一个 保持 未记录的行为而不是实际解决问题,当原因的每个细节都知道时?

分支预测错误
所有喧嚣的原因是分支预测错误的代价。
当一个分支出现时,CPU 预测采用的分支并在管道中预加载这些指令。
如果预测错误,则需要清除流水线并加载新指令。
这最多可能需要 number_of_stages_in_pipeline 个周期加上从缓存加载数据所需的任何周期。每次错误预测 14 到 25 个周期是典型的。

原因:处理器设计
K8 和 K10 出现这种情况的原因是因为 AMD 的巧妙优化。
AMD K8 和 K10 将在缓存中预解码指令,并在 CPU 一级指令缓存中跟踪它们的长度。
为了做到这一点,它有额外的位。

For every 128 bits (16 bytes) of instructions there are 76 bits of additional data stored

以下 table 对此进行了详细说明:

Data             Size       Notes
-------------------------------------------------------------------------
Instructions     128 bits   The data as read from memory
Parity bits      8 bits     One parity bit for every 16 bits
Pre-decode       56 bits    3 bits per byte (start, end, function) 
                            + 4 bit per 16 byte line
Branch selectors 16 bits    2 bits for each 2 bytes of instruction code

Total            204 bits   128 instructions, 76 metadata

因为所有这些数据都存储在 L1 指令缓存中,所以 K8/10 cpu 在解码和分支预测上花费的工作要少得多。这节省了硅。
而且因为 AMD 没有像 Intel 那样大的晶体管预算,所以它需要更智能地工作。

但是如果代码是esp。 tight a jump 和 ret 可能占用相同的两个字节槽,这意味着 RET 被预测为未被采用(因为紧随其后的跳转是)。
通过使 RET 占用两个字节 REP RET,这永远不会发生,并且 RET 将始终被预测为 OK。

Intel 没有这个问题,但是(曾经)受到预测槽数量有限的困扰,而 AMD 则没有。

nop ret
从来没有理由去做 nop ret。这是两条浪费额外周期来执行 nop 的指令,而 ret 可能仍然 'pair' 带有跳转。
如果要对齐,请改用 REP MOV 或使用 multibyte nop.

结束语
只有本地分支预测与指令一起存储在缓存中。
还有一个单独的全局分支预测 table。