repz ret
的问题已经在这里[1]以及其他来源[2,3]中得到了相当令人满意的讨论。然而,阅读这两个来源,我找到了以下答案:
-
在与
ret
或nop; ret
的定量比较中实际惩罚是多少?尤其是在后一种情况下——当大多数函数要么有100多条指令,要么被内联时,解码一条额外的指令(而且是空指令!)真的相关吗? -
为什么这个问题从来没有在AMD K8中得到解决,甚至在K10中得到解决?当每个细节的原因都是已知的时候,基于一个行为和保持未记录的方式来记录一个丑陋的解决方案,比实际解决问题更可取吗?
分支预测错误
所有这些喧嚣的原因都是分支错误预测的代价。当一个分支出现时,CPU预测该分支被占用,并在管道中预加载这些指令。
如果预测错误,则需要清除管道并加载新指令。
这可能需要number_of_stages_in_pipeline
周期加上从缓存加载数据所需的任何周期。典型的错误预测是14到25个周期。
原因:处理器设计
K8和K10之所以会出现这种情况,是因为AMD进行了一项巧妙的优化。AMD K8和K10将在缓存中预解码指令,并在CPU L1指令缓存中跟踪它们的长度。为了做到这一点,它有额外的位。
对于每128位(16字节)的指令,有76位额外的数据存储。
下表详细说明:
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没有英特尔那么大的晶体管预算,它需要更聪明地工作。
然而,如果代码特别紧凑,跳转和ret可能占用相同的两个字节槽,这意味着在那里RET
被预测为未被占用(因为它后面的跳转是)。
通过使RET占用两个字节REP RET
,这种情况永远不会发生,并且RET将始终被预测为OK。
英特尔没有这个问题,但(过去)受到有限数量的预测插槽的困扰,而AMD没有。
nop ret
没有理由去做nop ret
。这两条指令浪费了一个额外的周期来执行nop
,而ret
可能仍然会与跳转"配对"。如果你想要对齐,使用REP MOV
或multibyte nop
。
闭幕词
只有本地分支预测与指令一起存储在缓存中。
还有一个单独的Global分支预测表。