以下哪些代码会更有效,为什么?
(编辑:更新,状态现在是对象的成员)
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;
}
答案 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个汇编指令以检查状态将必须支付。如果没有,请忽略
if
当x
特别大时。