goto对C ++编译器优化的影响

时间:2012-04-30 15:21:57

标签: c++ optimization compiler-optimization goto

在现代C ++编译器中使用goto有什么性能优势或惩罚?

我正在编写一个C ++代码生成器,使用goto会使编写更容易。 没有人会触及生成的C ++文件,所以不要让所有“goto对我不好”。作为一个好处,他们可以节省临时变量的使用。

从纯粹的编译器优化角度来看,我想知道goto对编译器优化器的结果是什么?与使用临时/标志相比,它是否使代码更快更慢,或者通常无变化

4 个答案:

答案 0 :(得分:22)

受影响的编译器部分使用流程图。用于创建特定流程图的语法通常是无关紧要的 - 如果使用while而不是实际的goto语句创建类似while循环的内容,则不会完全影响优化器(到那时,生成流程图的语法将很快消失)。

但是,有可能生成一个带有goto s的流程图,这些流程图不能由任何正常的流程控制语句(循环,开关等)生成。在这种情况下,您可能会产生一个不可简化的流程图,以及何时/如果这样做,通常会限制编译器优化代码的能力。

换句话说,如果(例如)您使用了使用普通forwhileswitch等编写的代码,并将其转换为使用{{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的回答,但我只有一点要补充。

当人们被教授编译器时,他们会被告知编译器可以做的所有精彩优化。

他们没有被告知这个的实际价值取决于用户是谁。

如果您正在编写(或生成)和编译的代码包含很少的函数调用,并且本身可能会占用其他程序的大部分时间,那么编译器优化很重要。

如果生成的代码包含函数调用,或者由于某些其他原因,程序计数器在生成的代码中花费了一小部分时间,那么就不值得担心了。 为什么?因为即使该代码可以如此积极地进行优化以使其花费时间,它也可以节省不超过一小部分,并且可能存在更大的性能问题,编译器无法修复,很乐意回避你的注意力。