在现代C ++编译器中使用goto
有什么性能优势或惩罚?
我正在编写一个C ++代码生成器,使用goto
会使编写更容易。 没有人会触及生成的C ++文件,所以不要让所有“goto对我不好”。作为一个好处,他们可以节省临时变量的使用。
从纯粹的编译器优化角度来看,我想知道goto对编译器优化器的结果是什么?与使用临时/标志相比,它是否使代码更快,更慢,或者通常无变化。
答案 0 :(得分:22)
受影响的编译器部分使用流程图。用于创建特定流程图的语法通常是无关紧要的 - 如果使用while
而不是实际的goto
语句创建类似while
循环的内容,则不会完全影响优化器(到那时,生成流程图的语法将很快消失)。
但是,有可能生成一个带有goto
s的流程图,这些流程图不能由任何正常的流程控制语句(循环,开关等)生成。在这种情况下,您可能会产生一个不可简化的流程图,以及何时/如果这样做,通常会限制编译器优化代码的能力。
换句话说,如果(例如)您使用了使用普通for
,while
,switch
等编写的代码,并将其转换为使用{{1}在每种情况下,但保留相同的结构,几乎任何合理的现代编译器都可能产生基本相同的代码。但是,如果你使用goto
来制作像几十年前我必须看到的FORTRAN的大量意大利面,那么编译器可能无法用它做很多事情。
答案 1 :(得分:6)
我想知道,从纯编译器优化的角度来看,goto对编译器优化器的结果是什么?与使用临时/标志相比,它是否使代码更快,更慢或通常没有性能变化。
为什么要关心?您主要关注的是让代码生成器创建正确的代码。效率远不如正确性重要。你的问题应该是“我使用gotos会使我生成的代码更可能或更不可能正确吗?”
查看lex / yacc或flex / bison生成的代码。那段代码充满了getos。这是有充分理由的。 lex和yacc实现有限状态机。由于机器在状态转换时进入另一个状态,goto可以说是这种转换的最自然的工具。
通过在while
语句周围使用switch
循环,有一种简单的方法可以在许多情况下消除这些。这是结构化代码。 Per Douglas Jones(Jones DW,如何(不)编写有限状态机,SIGPLAN Not。23,8(1988年8月),19-22。),这是最糟糕的编码方式FSM。他认为基于goto的方案更好。
他还认为,有一种更好的方法,即使用图论技术将FSM转换为控制流程图。这并不容易。这是NP难题。这就是为什么你仍然会看到很多FSM,特别是自动生成的FSM,实现为围绕交换机的循环或通过gotos实现的状态转换。
答案 2 :(得分:5)
您如何认为在汇编级别表示循环?
使用跳转说明标签 ...
许多编译器实际上甚至会在他们的中级表示中使用跳转:
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
是优化者的基础。
答案 3 :(得分:3)
我衷心同意David Hammen的回答,但我只有一点要补充。
当人们被教授编译器时,他们会被告知编译器可以做的所有精彩优化。
他们没有被告知这个的实际价值取决于用户是谁。
如果您正在编写(或生成)和编译的代码包含很少的函数调用,并且本身可能会占用其他程序的大部分时间,那么编译器优化很重要。
如果生成的代码包含函数调用,或者由于某些其他原因,程序计数器在生成的代码中花费了一小部分时间,那么就不值得担心了。 为什么?因为即使该代码可以如此积极地进行优化以使其花费零时间,它也可以节省不超过一小部分,并且可能存在更大的性能问题,编译器无法修复,很乐意回避你的注意力。