"goto" 语句如何影响 CPU 的 "branch prediction"?
How does "goto" statements affect the "branch prediction" of the CPU?
为了进一步了解CPU和代码优化我已经开始研究汇编编程了。我还阅读了 "branch prediction" 等巧妙的优化,CPU 确实可以加快自身速度。
我的问题可能看起来很愚蠢,因为我还不太了解这个主题。
我有一个非常模糊的记忆,我在某个地方(在互联网上)读到 goto
语句会降低程序的性能,因为它不能很好地与 [=31 中的分支预测一起工作=].然而,这可能只是我编造的东西,并没有真正阅读。
我认为这可能是真的。
我希望这个例子(在伪 C 中)能阐明我为什么这么认为:
int function(...) {
VARIABLES DECLARED HERE
if (HERE IS A TEST) {
CODE HERE ...
} else if (ANOTHER TEST) {
CODE HERE ...
} else {
/*
Let us assume that the CPU was smart and predicted this path.
What about the jump to `label`?
Is it possible for the CPU to "pre-fetch" the instructions over there?
*/
goto label;
}
CODE HERE...
label:
CODE HERE...
}
对我来说,这似乎是一项非常复杂的任务。那是因为 CPU 将需要查找 goto
跳转到的位置,以便能够预取那里的指令。
你知道这件事吗?
由于 'pipelining' 和类似活动,
分支指令实际上可以放置几条指令
在实际分支发生的位置之前。
(这是编译器中分支预测逻辑的一部分)。
goto 语句只是一个跳转指令。
作为旁注:
给定结构化编程概念,
代码清晰度、可读性、可维护性考虑等;
永远不要使用 'goto' 语句。
在大多数 CPU 上,
任何 jump/call/return 类型的指令都会刷新预取缓存
然后从新位置重新加载该缓存,如果是新位置
尚未在缓存中。
注意:对于小循环,
它将始终包含 'at least' 一条跳转指令,
许多 CPU 都有一个程序员可以利用的内部缓冲区
使小循环只执行一个预取序列
因此执行速度提高了很多个数量级。
无条件分支对于分支预测器来说不是问题,因为分支预测器不必预测它们。
它们为推测性指令获取单元增加了一些复杂性,因为分支(和其他改变指令指针的指令)的存在意味着指令并不总是以线性顺序获取。当然,这也适用于条件分支。
记住,分支预测和推测执行是不同的东西。推测性执行不需要分支预测:假设从未采用分支,您可以推测性地执行代码,如果您确实采用了一个分支,则取消该分支之外的所有操作。在无条件分支的情况下,这将是一件特别愚蠢的事情,但它会使逻辑保持良好和简单。 (IIRC,这就是第一个流水线处理器的工作方式。)
(我想你可以在没有推测执行的情况下进行分支预测,但实际上没有意义,因为分支预测器没有任何人可以告诉它的预测。)
所以是的,分支——有条件的和无条件的——增加了指令获取单元的复杂性。没关系。 CPU 建筑师是一些非常聪明的人。
编辑:在糟糕的过去,人们观察到 goto
语句的使用会对当时编译器优化代码的能力产生不利影响。这可能是你的想法。现代编译器更聪明,通常不会对 goto
.
感到吃惊
为了进一步了解CPU和代码优化我已经开始研究汇编编程了。我还阅读了 "branch prediction" 等巧妙的优化,CPU 确实可以加快自身速度。
我的问题可能看起来很愚蠢,因为我还不太了解这个主题。
我有一个非常模糊的记忆,我在某个地方(在互联网上)读到 goto
语句会降低程序的性能,因为它不能很好地与 [=31 中的分支预测一起工作=].然而,这可能只是我编造的东西,并没有真正阅读。
我认为这可能是真的。
我希望这个例子(在伪 C 中)能阐明我为什么这么认为:
int function(...) {
VARIABLES DECLARED HERE
if (HERE IS A TEST) {
CODE HERE ...
} else if (ANOTHER TEST) {
CODE HERE ...
} else {
/*
Let us assume that the CPU was smart and predicted this path.
What about the jump to `label`?
Is it possible for the CPU to "pre-fetch" the instructions over there?
*/
goto label;
}
CODE HERE...
label:
CODE HERE...
}
对我来说,这似乎是一项非常复杂的任务。那是因为 CPU 将需要查找 goto
跳转到的位置,以便能够预取那里的指令。
你知道这件事吗?
由于 'pipelining' 和类似活动, 分支指令实际上可以放置几条指令 在实际分支发生的位置之前。 (这是编译器中分支预测逻辑的一部分)。
goto 语句只是一个跳转指令。
作为旁注: 给定结构化编程概念, 代码清晰度、可读性、可维护性考虑等; 永远不要使用 'goto' 语句。
在大多数 CPU 上, 任何 jump/call/return 类型的指令都会刷新预取缓存 然后从新位置重新加载该缓存,如果是新位置 尚未在缓存中。
注意:对于小循环, 它将始终包含 'at least' 一条跳转指令, 许多 CPU 都有一个程序员可以利用的内部缓冲区 使小循环只执行一个预取序列 因此执行速度提高了很多个数量级。
无条件分支对于分支预测器来说不是问题,因为分支预测器不必预测它们。
它们为推测性指令获取单元增加了一些复杂性,因为分支(和其他改变指令指针的指令)的存在意味着指令并不总是以线性顺序获取。当然,这也适用于条件分支。
记住,分支预测和推测执行是不同的东西。推测性执行不需要分支预测:假设从未采用分支,您可以推测性地执行代码,如果您确实采用了一个分支,则取消该分支之外的所有操作。在无条件分支的情况下,这将是一件特别愚蠢的事情,但它会使逻辑保持良好和简单。 (IIRC,这就是第一个流水线处理器的工作方式。)
(我想你可以在没有推测执行的情况下进行分支预测,但实际上没有意义,因为分支预测器没有任何人可以告诉它的预测。)
所以是的,分支——有条件的和无条件的——增加了指令获取单元的复杂性。没关系。 CPU 建筑师是一些非常聪明的人。
编辑:在糟糕的过去,人们观察到 goto
语句的使用会对当时编译器优化代码的能力产生不利影响。这可能是你的想法。现代编译器更聪明,通常不会对 goto
.