Does the second version avoid a branch
如果你编译你的代码以获得汇编输出,g++ -o test.s test.cpp -S,你会发现在第二个函数中确实避免了一个分支。
and consequently, is faster than the first version
我运行您的每个函数2147483647 或INT_MAX 的次数,在每次迭代中我随机分配一个布尔值给Timer 结构的running 成员,使用以下代码:
int main() {
const int max = std::numeric_limits<int>::max();
timestamp_t start, end, one, two;
Timer t_one, t_two;
double percent;
srand(time(NULL));
start = get_timestamp();
for(int i = 0; i < max; ++i) {
t_one.running = rand() % 2;
t_one.step_versionOne(1);
}
end = get_timestamp();
one = end - start;
std::cout << "step_versionOne = " << one << std::endl;
start = get_timestamp();
for(int i = 0; i < max; ++i) {
t_two.running = rand() % 2;
t_two.step_versionTwo(1);
}
end = get_timestamp();
two = end - start;
percent = (one - two) / static_cast<double>(one) * 100.0;
std::cout << "step_versionTwo = " << two << std::endl;
std::cout << "step_one - step_two = " << one - two << std::endl;
std::cout << "one fast than two by = " << percent << std::endl;
}
这些是我得到的结果:
step_versionOne = 39738380
step_versionTwo = 26047337
step_one - step_two = 13691043
one fast than two by = 34.4529%
所以是的,第二个函数显然更快,大约 35%。请注意,对于较少的迭代次数,定时性能的百分比增加在 30% 到 55% 之间变化,而运行时间越长,它似乎稳定在 35% 左右。这可能是由于在模拟运行时系统任务的零星执行,这变得不那么零星了,即运行 sim 的时间越长就越一致(虽然这只是我的假设,我不知道它是否真的是真的)
总之,好问题,我今天学到了一些东西!
更多:
当然,通过随机生成running,我们实质上是在第一个函数中使分支预测无用,所以上面的结果并不令人惊讶。但是,如果我们决定在循环迭代期间不更改 running 而是将其保留为默认值,在本例中为 false,分支预测将在第一个函数中发挥其魔力,实际上会快近 20%正如这些结果所示:
step_versionOne = 6273942
step_versionTwo = 7809508
step_two - step_one = 1535566
two fast than one by = 19.6628
因为running 在整个执行过程中保持不变,请注意模拟时间比随机变化的running 短得多 - 可能是编译器优化的结果。
为什么在这种情况下第二个函数变慢了?好吧,分支预测将很快意识到第一个函数中的条件从未满足,因此将首先停止检查(好像if(running) ticks += mStepSize; 甚至不存在)。另一方面,第二个函数仍然必须在每次迭代中执行这条指令ticks += mStepSize * static_cast<int>(running);,从而使第一个函数更高效。
但是如果我们将running 设置为true 会怎样?好吧,分支预测将再次启动,但是,这一次,第一个函数必须在每次迭代中评估 ticks += mStepSize;;这里是running{true}时的结果:
step_versionOne = 7522095
step_versionTwo = 7891948
step_two - step_one = 369853
two fast than one by = 4.68646
请注意,无论running 始终是true 还是false,step_versionTwo 所花费的时间都是一致的。但它仍然比step_versionTwo 花费更长的时间,但时间很短。好吧,这可能是因为我懒得运行它很多次来确定它是否始终更快或者它是否是一次侥幸(每次运行它时结果都会略有不同,因为操作系统必须在后台运行它并不总是会做同样的事情)。但如果它始终更快,可能是因为函数二 (ticks += mStepSize * static_cast<int>(running);) 的算术运算比函数一 (ticks += mStepSize;) 多。
最后,让我们进行优化 - g++ -o test test.cpp -std=c++11 -O1 进行编译,然后将 running 还原为 false,然后检查结果:
step_versionOne = 704973
step_versionTwo = 695052
大致相同。编译器将执行其优化传递,并意识到running 始终是false,因此,出于所有意图和目的,将删除step_versionOne 的主体,因此当您从main 的循环中调用它时,它'只会调用函数并返回。
另一方面,在优化第二个函数时,它会意识到ticks += mStepSize * static_cast<int>(running);总是会产生相同的结果,即0,所以它也不会费心去执行。
总而言之,如果我是正确的(如果不正确,请纠正我,我对此很陌生),从main 循环调用这两个函数时,您将得到的只是它们的开销。
附言这是第一种情况的结果(running 在每次迭代中随机生成)在使用优化编译时。
step_versionOne = 18868782
step_versionTwo = 18812315
step_two - step_one = 56467
one fast than two by = 0.299261