gcc能否根据条件优化我的周期?

时间:2011-04-02 11:38:34

标签: c++ c optimization gcc

我有以下周期:

//condition will be set here to true or false

for (int i = 0; i < LARGE_NUMBER; i++) {
    if (condition) {
        //do foo
    } else {
        //do bar
    }
}

假设:没有条件而不是条件的周期更快。 (这是真的?) 问题:如果在{for循环之外设置了if并且循环本身没有触及condition,那么gcc会将condition分解出来吗?

如果没有,我应该切换iffor,重复代码,违反DRY等。

3 个答案:

答案 0 :(得分:17)

对于那些不想阅读冗长帖子的人,可以调用此优化(在LLVM中)Loop Unswitch

为什么不问编译器?

void foo(char* c);

int main(int argc, char **argv) {
  bool const condition = argc % 2;

  for (int i = 0; i != argc; ++i) {
    if (condition) {
      foo(argv[1]);
    } else {
      foo(argv[0]);
    }
  }
  return 0; 
}

转换为SSA形式(通过LLVM try out):

define i32 @main(i32 %argc, i8** nocapture %argv) {
entry:
  %0 = icmp eq i32 %argc, 0                       ; <i1> [#uses=1]
  br i1 %0, label %bb5, label %bb.nph

bb.nph:                                           ; preds = %entry
  %1 = and i32 %argc, 1                           ; <i32> [#uses=1]
  %toBool = icmp eq i32 %1, 0                     ; <i1> [#uses=1]
  %2 = getelementptr inbounds i8** %argv, i64 1   ; <i8**> [#uses=1]
  br i1 %toBool, label %bb3.us, label %bb3

bb3.us:                                           ; preds = %bb3.us, %bb.nph
  %i.07.us = phi i32 [ %4, %bb3.us ], [ 0, %bb.nph ] ; <i32> [#uses=1]
  %3 = load i8** %argv, align 8                   ; <i8*> [#uses=1]
  tail call void @_Z3fooPc(i8* %3)
  %4 = add nsw i32 %i.07.us, 1                    ; <i32> [#uses=2]
  %exitcond = icmp eq i32 %4, %argc               ; <i1> [#uses=1]
  br i1 %exitcond, label %bb5, label %bb3.us

bb3:                                              ; preds = %bb3, %bb.nph
  %i.07 = phi i32 [ %6, %bb3 ], [ 0, %bb.nph ]    ; <i32> [#uses=1]
  %5 = load i8** %2, align 8                      ; <i8*> [#uses=1]
  tail call void @_Z3fooPc(i8* %5)
  %6 = add nsw i32 %i.07, 1                       ; <i32> [#uses=2]
  %exitcond8 = icmp eq i32 %6, %argc              ; <i1> [#uses=1]
  br i1 %exitcond8, label %bb5, label %bb3

bb5:                                              ; preds = %bb3, %bb3.us, %entry
  ret i32 0
}

也许不太可读,所以让我指出这里有什么:

  • entry:检查argc是否等于0,如果是,请转到bb5(退出),否则转到bb.nph
  • bb.nph:计算condition的值,如果是,请转到bb3.us否则转到bb3
  • bb3.usbb3:分别为真假条件循环
  • bb5:退出

只要效果与您要求的类似,编译器就可以将代码转换为它想要的方式。在这种情况下,它有效地将代码重写为:

int main(int argc, char**argv) {
  if (argc != 0)
  {
    int i = 0;
    if (argc % 2) {
      do {
        foo(argv[1]);
        ++i;
      } while (i != argc);
    } else {
      do {
        foo(argv[0]);
        ++i;
      } while (i != argc);
    }
  }
  return 0;
}

这是循环不变优化的一种形式,这里结合第一次检查以避免在循环不会被执行时计算条件。

对于我们这些认为第一个解决方案更清晰的人来说,我们很高兴让编译器为我们进行细节优化!

答案 1 :(得分:8)

如果condition可以证明在迭代期间不会改变,那么任何体面的优化编译器都会这样做。

此外,即使编译器实际上没有这样做,您也可以支持您使用分析中的硬数据将代码重写为不太人类可读的形式的决定。 不要过早优化。给代码的读者一个“嗯?”是不值得的。为了减少几毫秒的时间(“读者”肯定包括你自己在未来的时间)。

答案 2 :(得分:5)

我不主张通过通常的“过早优化”论点采取任何行动。保持代码清晰是最重要的,如果整个程序太慢,您可能需要在程序彻底调试后分析并找到实际的瓶颈(通常无法猜测)。

即使编译器没有为您优化此特定情况,您可能想知道CPU执行某种形式的branch prediction,这将大大减少处理条件所需的时间,以防条件可预测。

实际上,pipeline中的大多数CPU处理指令,并且必须确定跳转地址时,条件变量可能是未知的。这将导致管道停顿,这是大多数现代处理器试图猜测(事实上聪明)程序将跳转的地方。如果条件变量确实已知(就像你的情况那样),猜测将是完美的。

所以我怀疑即使使用“哑”编译器,你也会看到两个选项之间存在差异,至少在现代机器上是这样。