21

goto使用现代 C++ 编译器有哪些性能优势或劣势?

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

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

4

4 回答 4

22

将受到影响的编译器部分与流程图一起使用。只要您编写严格可移植的代码,用于创建特定流程图的语法通常是无关紧要的——如果您使用 a而不是实际语句创建类似while循环的东西,它不会产生相同的流程图就好像您使用循环的语法一样。然而,使用不可移植的代码,现代编译器允许您向循环添加注释以预测它们是否会被采用。根据编译器,您可能会或可能无法使用 a 复制该额外信息(但大多数具有循环注释的循环也具有语句注释,因此 a或在控制 agotowhilewhilegotoiflikely takenlikely not takenifgoto通常与相应循环上的类似注释具有相同的效果)。

但是,可以生成一个带有gotos 的流图,这是任何正常的流控制语句(循环、开关等)都无法生成的,例如有条件地直接跳转到循环的中间,具体取决于一个全球性的。在这种情况下,您可能会生成一个不可约的流程图,并且当/如果您这样做时,这通常会限制编译器优化代码的能力。

换句话说,如果(例如)您将使用 normal for, while,switch等编写的代码转换为goto在每种情况下都可以使用,但保留相同的结构,则几乎任何合理的现代编译器都可能生成基本相同的代码方法。但是,如果您使用gotos 来产生像我几十年前不得不看的一些 FORTRAN 一样的意大利面条,那么编译器可能无法用它做很多事情。

于 2012-04-30T15:34:40.310 回答
8

您如何看待循环在程序集级别上的表示?

使用跳转指令到标签...

许多编译器实际上会在它们的中间表示中使用跳转:

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优化器的生计就是如此。

于 2012-04-30T15:25:03.120 回答
4

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

你为什么在乎?您最关心的应该是让您的代码生成器创建正确的代码。效率远不如正确性重要。您的问题应该是“我对 goto 的使用会使我生成的代码更可能还是不太可能是正确的?”

查看 lex/yacc 或 flex/bison 生成的代码。该代码充满了goto。这是有充分理由的。lex 和 yacc 实现有限状态机。由于机器在状态转换时会进入另一个状态,因此 goto 可以说是这种转换最自然的工具。

在许多情况下,有一种简单的方法可以通过在语句while周围使用循环来消除这些 goto。switch这是结构化代码。Per Douglas Jones(Jones DW,如何(不)编写有限状态机,SIGPLAN Not. 23, 8 (Aug. 1988), 19-22.),这是对 FSM 进行编码的最糟糕的方法。他认为基于 goto 的方案更好。

他还认为还有一种更好的方法,即使用图论技术将 FSM 转换为控制流程图。这并不总是那么容易。这是一个NP难题。这就是为什么您仍然会看到很多 FSM,尤其是自动生成的 FSM,它们被实现为围绕开关的循环或通过 goto 实现的状态转换。

于 2012-04-30T16:46:27.440 回答
1

我非常同意 David Hammen 的回答,但我只有一点要补充。

当人们被教导编译器时,他们会被教导编译器可以做的所有美妙的优化。

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

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

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

于 2012-04-30T18:09:09.873 回答