状态检查总是一件有效的事情吗


  • 假设只需要将一个值绑定到某个当bState为true时,某个对象的数据成员。当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;
}

我认为后者肯定更快。第一个版本具有双向内存访问,后者具有单向内存访问。

在此版本中:

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

由于CPU必须等待从存储器读取数据,因此在CCD_。读取内存的速度取决于数据所在的位置。离CPU越远,所需时间越长:L1(最快(、L2、L3、Ram、Disk(最慢(。

在此版本中:

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

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

除了内存访问时间外,后一个循环在循环内没有条件跳转。条件循环是一个很大的开销,特别是如果已采取/未采取的决策实际上是随机的。

这一切都取决于您为帖子简化了什么。如果添加一个分支只是为了跳过设置变量,那么如果分支预测失败,您可能不会获得任何结果,也可能会失去结果。我会取消测试。

现在,如果要更新的对象不是简单的int,那么。。。和往常一样,测量、分析,然后根据实际情况而不是直觉做出决定。如果这不是一个紧密循环的一部分,那么无论哪种方式,你都可能不会注意到差异。

你听说过循环不变代码运动吗?

它是来自编译器的优化传递,可以在任何可能的时候将代码从循环体中移出。

例如,给定以下代码:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
  for (int i = 0; i < argc; ++i) {
    if (argc < 100) {
      printf("%dn", 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("%dn", atoi(argv[1]));
  }
  return 0;
}

结论:除非探查器显示它们并不像您想象的那样微观,否则不要考虑微观优化。

编辑:

编辑从根本上改变了这个问题(天哪,我讨厌这样:p(。LICM不再适用,并且这两个功能具有完全不同的功能。

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

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

我认为这实际上取决于上下文。如果对如果bState在绑定期间为true,则必须为每个循环迭代额外的1或2条用于检查状态的汇编指令付费。如果没有,请忽略if当CCD_ 9特别大时。

最新更新