【发布时间】:2015-12-24 17:33:45
【问题描述】:
我刚刚偶然发现了这个东西,我真的很好奇现代 CPU(当前的 CPU,也可能是移动的(嵌入式))在下面的情况下是否真的没有分支成本。
1.假设我们有这个:
x += a; // let's assume they are both declared earlier as simple ints
if (flag)
do A // let's assume A is not the same as B
else
do B // and of course B is different than A
2.与此相比:
if (flag)
{
x += a
do A
}
else
{
x += a
do B
}
假设A 和B 在流水线指令(获取、解码、执行等)方面完全不同:
第二种方法会更快吗?
CPU 是否足够聪明,可以判断无论标志是什么,下一条指令都是相同的(因此它们不必因为分支未命中预测而丢弃流水线阶段)?
注意:
在第一种情况下,CPU 没有选择,只能丢弃 do A 的前几个管道阶段,如果发生分支未命中预测,则执行 B,因为它们是不同的。我将第二个示例视为某种延迟的分支,例如: " 我将检查该标志,即使我不知道该标志,我也可以继续执行下一条指令,因为它是相同的,无论如何标志是什么,我已经有了下一条指令,我可以使用它。”
编辑:
我做了一些研究,得到了一些不错的结果。你会如何解释这种行为?抱歉我最近的编辑,但据我所知,我有一些缓存问题,我希望这些是更准确的结果和代码示例。
这是代码,使用 gcc 版本 4.8.2 (Ubuntu 4.8.2-19ubuntu1) 使用 -O3 编译。
案例 1。
#include <stdio.h>
extern int * cache;
extern bool * b;
extern int * x;
extern int * a;
extern unsigned long * loop;
extern void A();
extern void B();
int main()
{
for (unsigned long i = 0; i < *loop; ++i)
{
++*cache;
*x += *a;
if (*b)
{
A();
}
else
{
B();
}
}
delete b;
delete x;
delete a;
delete loop;
delete cache;
return 0;
}
int * cache = new int(0);
bool * b = new bool(true);
int * x = new int(0);
int * a = new int(0);
unsigned long * loop = new unsigned long(0x0ffffffe);
void A() { --*x; *b = false; }
void B() { ++*x; *b = true; }
案例 2
#include <stdio.h>
extern int * cache;
extern bool * b;
extern int * x;
extern int * a;
extern unsigned long * loop;
extern void A();
extern void B();
int main()
{
for (unsigned long i = 0; i < *loop; ++i)
{
++*cache;
if (*b)
{
*x += *a;
A();
}
else
{
*x += *a;
B();
}
}
delete b;
delete x;
delete a;
delete loop;
delete cache;
return 0;
}
int * cache = new int(0);
bool * b = new bool(true);
int * x = new int(0);
int * a = new int(0);
unsigned long * loop = new unsigned long(0x0ffffffe);
void A() { --*x; *b = false; }
void B() { ++*x; *b = true; }
两种方法的 -O3 版本之间几乎没有明显的差异,但没有 -O3,第二种情况的运行速度会稍微快一些,至少在我的机器上是这样。
我在没有 -O3 和循环 = 0xffffffffe 的情况下进行了测试。
最佳时间:
alin@ubuntu:~/Desktop$ time ./1
真正的 0m20.231s
用户 0m20.224s
系统 0m0.020s
alin@ubuntu:~/Desktop$ 时间./2
真正的 0m19.932s
用户 0m19.890s
系统 0m0.060s
【问题讨论】:
-
这样的东西一般是由编译器优化的,而不是在执行/CPU级别。
-
我怀疑编译器优化器会完成它的工作并将其分解以产生相同的代码。
-
PS:感谢您的代码编辑(这是我的第一篇文章,对此感到抱歉)。所以换句话说,我可以把 case 2 写成 1 并相信编译器会注意到这一点?
-
@Calvin 排除通用代码会导致优化尝试失败。
-
@AlinIonutLipan:我还没有看到 x86 机器上的编译器这样做(将案例 1 转换为案例 2),但我在几十年前在 RISC 机器上已经看到过瘦(但是不完全像这样。)这确实是由编译器完成的。一般来说,你不能过多的依赖编译器优化,但是这个是比较简单和明显的针孔优化。我建议总是写 case 1,因为编译器更容易做到。
标签: c++ c pipeline branch-prediction