goto 对C++编译器优化的影响
effect of goto on C++ compiler optimization
goto
与现代C++编译器一起使用的性能优势或惩罚是什么?
我正在编写一个C++代码生成器,使用 goto
将使编写更容易。没有人会碰生成的C++文件,所以不要把所有的"goto 不好"都放在我身上。好处是,它们节省了临时变量的使用。
我想知道,从纯粹的编译器优化的角度来看,goto 对编译器的优化器有什么结果?与使用临时/标志相比,它是否使代码更快、更慢或通常没有性能变化。
编译器中受影响的部分与流图一起工作。只要您编写严格可移植的代码,用于创建特定流图的语法通常无关紧要 - 如果您使用 goto
而不是实际的 while
语句创建类似 while
循环的东西,它不会生成与使用while
循环语法相同的流图。但是,使用非可移植代码,现代编译器允许您向循环添加注释,以预测它们是否会被采用。根据编译器的不同,您可能能够也可能无法使用goto
复制该额外信息(但大多数具有循环注释的语句也有if
语句的注释,因此控制goto
的if
上的likely taken
或likely not taken
通常与相应循环上的类似注释具有相同的效果(。
但是,可以生成一个具有goto
的流图,而这些流图无法由任何正常的流控制语句(循环、开关等(生成,例如有条件地直接跳转到循环的中间,具体取决于全局中的值。在这种情况下,您可能会生成一个不可约的流图,当您这样做时/如果这样做,这通常会限制编译器优化代码的能力。
换句话说,如果(例如(你采用用普通for
、while
、switch
等编写的代码,并将其转换为在每种情况下都使用goto
,但保留相同的结构,几乎任何相当现代的编译器都可能以任何一种方式生成基本相同的代码。但是,如果您使用goto
来产生混乱的意大利面条,就像我几十年前不得不查看的一些FORTRAN一样,那么编译器可能无法用它做太多事情。
您认为循环在程序集级别是如何表示的?
使用跳转指令来标记...
许多编译器实际上甚至在他们的中间表示中使用跳转:
int loop(int* i) {
int result = 0;
while(*i) {
result += *i;
}
return result;
}
int jump(int* i) {
int result = 0;
while (true) {
if (not *i) { goto end; }
result += *i;
}
end:
return result;
}
LLVM 中的收益率:
define i32 @_Z4loopPi(i32* nocapture %i) nounwind uwtable readonly {
%1 = load i32* %i, align 4, !tbaa !0
%2 = icmp eq i32 %1, 0
br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge
.lr.ph..lr.ph.split_crit_edge: ; preds = %.lr.ph..lr.ph.split_crit_edge, %0
br label %.lr.ph..lr.ph.split_crit_edge
; <label>:3 ; preds = %0
ret i32 0
}
define i32 @_Z4jumpPi(i32* nocapture %i) nounwind uwtable readonly {
%1 = load i32* %i, align 4, !tbaa !0
%2 = icmp eq i32 %1, 0
br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge
.lr.ph..lr.ph.split_crit_edge: ; preds = %.lr.ph..lr.ph.split_crit_edge, %0
br label %.lr.ph..lr.ph.split_crit_edge
; <label>:3 ; preds = %0
ret i32 0
}
其中br
是分支指令(条件跳转(。
所有优化都在此结构上执行。因此,goto
是优化器的面包和黄油。
我想知道,从纯粹的编译器优化来看,goto对编译器的优化器的结果是什么?与使用临时/标志相比,它是否使代码更快、更慢或通常没有性能变化。
你为什么关心?您主要关心的应该是让代码生成器创建正确的代码。效率远不如正确性重要。你的问题应该是"我对gotos的使用是否会使我生成的代码更有可能或更不可能是正确的?
看看 lex/yacc 或 flex/bison 生成的代码。该代码充满了gotos。这是有充分理由的。lex 和 yacc 实现了有限状态机。由于机器在状态转换时会进入另一种状态,因此 goto 可以说是这种转换最自然的工具。
在许多情况下,有一种简单的方法可以通过在switch
语句周围使用while
循环来消除这些 goto。这是结构化代码。根据Douglas Jones(Jones D. W.,如何(不(编码有限状态机,SIGPLAN Not.23,8(Aug.1988(,19-22.(,这是编码FSM的最糟糕的方法。他认为基于goto的方案更好。
他还认为,还有一种更好的方法,即使用图论技术将FSM转换为控制流图。这并不总是那么容易。这是一个NP难题。这就是为什么你仍然看到很多FSM,特别是自动生成的FSM,要么是围绕开关的循环,要么是通过gotos实现的状态转换。
我衷心同意David Hammen的回答,但我只有一点要补充。
当人们学习编译器时,他们被教导编译器可以做的所有精彩优化。
他们没有被告知它的实际价值取决于用户是谁。
如果您正在编写(或生成(和编译的代码包含很少的函数调用,并且本身可能会消耗其他程序的大部分时间,那么是的,编译器优化很重要。
如果生成的代码包含函数调用,或者由于某种其他原因,程序计数器在生成的代码中花费了一小部分时间,则不值得担心。为什么?因为即使该代码可以如此积极地优化,以至于花费零时间,它也不会节省超过一小部分,并且可能存在编译器无法修复的更大的性能问题,这些问题很乐意逃避您的注意。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- C 中编译器优化的影响
- 与任何算术操作员都会影响优化的恒定操作数顺序
- gcc优化对具有明显恒定变量的循环的影响
- 对长度使用"size_t"会影响编译器优化
- 编译器优化如何影响数据加载速度
- C++优化级别是否会影响Swig Python模块的性能
- 实现move构造函数如何影响返回值优化
- 编译器优化如何影响代码逻辑
- 是否存在算术运算受到编译器优化影响的情况
- GCC 4.6.3 - 模板专用化受优化级别的影响
- goto 对C++编译器优化的影响
- 是否使用 Guard 类免受编译器优化和 CPU 重新排序的影响
- 为什么C++去构造器会影响收益值优化的行为
- g++优化选项会影响sin函数的值
- LTO优化负面影响并找到最佳解决方案
- Visual Studio警告C4743:整个程序优化如何影响_fltused