状态检查总是有效的吗?

时间:2011-10-25 14:09:20

标签: c++ performance

  • 假设只需要将值绑定到某个值 bState为true时某个对象的datamember。当bState 是假的,没有必要,但也不妨碍。

以下哪些代码会更有效,为什么?

(编辑:更新,状态现在是对象的成员)

const int x;     
int i;
int iToBind;
Classname pObject[x];

for (; i < x; ++i) {
 if (pObject[i].bState) {
        pObject[i].somedatamember = iToBind;
    }
}

对战:

for (; i < x; ++i) {
   pObject[i].somedatamember = iToBind;
}

5 个答案:

答案 0 :(得分:3)

我会说后者肯定更快。第一个版本具有双向内存访问权限,后者具有单向内存访问权限。

在此版本中:

for (; i < x; ++i) {
  if (pObject[x].bState) {
    pObject[x].somedatamember = iToBind;
  }
}

if语句期间有一个停顿,因为CPU必须等待从内存中读取数据。读取内存的速度取决于数据所在的位置。离CPU越远,所需时间越长:L1(最快),L2,L3,Ram,磁盘(最慢)。

在此版本中:

for (; i < x; ++i) {
  pObject[x].somedatamember = iToBind;
}

只有写入内存。写入内存不会使CPU停滞。

除了内存访问时间,后一个循环在循环内没有条件跳转。条件循环是一个重要的开销,特别是如果采取/不采取决策是有效随机的。

答案 1 :(得分:1)

你听说过Loop Invariant Code Motion吗?

这是来自编译器的优化传递,它尽可能地将代码移出循环体。

例如,给出以下代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  for (int i = 0; i < argc; ++i) {
    if (argc < 100) {
      printf("%d\n", atoi(argv[1]));
    }
  }
}

Clang生成以下IR:

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
  %1 = icmp sgt i32 %argc, 0
  br i1 %1, label %.lr.ph, label %._crit_edge

.lr.ph:                                           ; preds = %0
  %2 = icmp slt i32 %argc, 100
  %3 = getelementptr inbounds i8** %argv, i64 1
  br i1 %2, label %4, label %._crit_edge

; <label>:4                                       ; preds = %4, %.lr.ph
  %i.01.us = phi i32 [ %9, %4 ], [ 0, %.lr.ph ]
  %5 = load i8** %3, align 8, !tbaa !0
  %6 = tail call i64 @strtol(i8* nocapture %5, i8** null, i32 10) nounwind
  %7 = trunc i64 %6 to i32
  %8 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %7) nounwind
  %9 = add nsw i32 %i.01.us, 1
  %exitcond = icmp eq i32 %9, %argc
  br i1 %exitcond, label %._crit_edge, label %4

._crit_edge:                                      ; preds = %4, %.lr.ph, %0
  ret i32 0
}

哪些可以转换回C:

int main(int argc, char** argv) {
  if (argc == 0) { return 0; }

  if (argc >= 100) { return 0; }

  for (int i = 0; i < argc; ++i) {
    printf("%d\n", atoi(argv[1]));
  }

  return 0;
}

结论:不要为微观优化而烦恼,除非探查者发现他们不像你想象的那样微观。

修改

编辑从根本上改变了问题(上帝,我讨厌那个:p)。 LICM不再适用,这两个功能具有广泛不同的功能。

然而,结论仍然相同。 if循环内的单个for检查不会改变代码的基本复杂性(请记住,每次迭代都会测试循环条件......)。

答案 2 :(得分:1)

这一切都取决于你为帖子简化了什么。如果要添加一个分支只是为了跳过设置变量,那么你可能没有获得任何东西,如果分支预测失败可能会失去。我会删除测试。

现在,如果要更新的对象不是简单的int,那么......一如既往地测量,分析然后根据实际事实做出决定而不是预感。如果这不是紧密循环的一部分,那么你甚至都不会注意到这种差异。

答案 3 :(得分:0)

我所知道的bState在第一个片段的循环中没有改变,所以你可以将if放在外面,这显然更有效。

答案 4 :(得分:0)

我说这实际上取决于背景。如果它是至关重要的 bState在绑定期间为真,然后每循环迭代额外的1或2个汇编指令以检查状态将必须支付。如果没有,请忽略 ifx特别大时。