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

ere*_*non 9 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
    }
}
Run Code Online (Sandbox Code Playgroud)

假设:没有条件而不是条件的周期更快.(这是真的吗?)问题:请问gcc if是否因为我condition已经设置了,如果已经设置了for循环,那么循环本身不会触及condition

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

Mat*_* M. 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; 
}
Run Code Online (Sandbox Code Playgroud)

转换为SSA形式(通过LLVM试用):

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
}
Run Code Online (Sandbox Code Playgroud)

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

  • entry:检查if argc是否等于0,如果是,转到bb5(退出)否则转到bb.nph
  • bb.nph:计算值condition,如果是真的,转到bb3.us其他去bb3
  • bb3.usbb3:分别为true和false条件循环
  • 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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


Jon*_*Jon 8

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

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


Ale*_* C. 5

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

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

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

所以我怀疑即使使用"哑"编译器,你实际上会看到两个选项之间的差异,至少在现代机器上.