- 假设只需要将一个值绑定到某个当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特别大时。