某些软件(通常是面向性能的,例如 Linux 内核、DPDK)具有 C 帮助程序,用于影响分支预测。
我有一个绝对简单的代码片段(假设我知道a>b的百分比)来表示当某些逻辑嵌套时条件嵌套和应用
likely
/unlikely
的问题:
bool foo()
{
foo1(1);
foo2(2);
/* if (unlikely(a > b)) */
/* if (a > b)*/
{
puts("Ohhh!!! Rare case");
return true;
}
return false;
}
int main(void)
{
/* if (unlikely(foo())) */
/* if (foo()) */
{
puts("Azaza");
}
}
那么从理论角度来看,哪两行应该取消注释以获得更好的性能?
显然有3种方法可以帮助编译器进行分支预测:
1.
if (unlikely(a > b))
...
if (unlikely(foo()))
2.
if (a > b)
...
if (unlikely(foo()))
3.
if (unlikely(a > b))
...
if (foo())
理论上哪个最有效?为什么?
据我所知,如果条件变量
not在缓存中,则
likely/unlikely
语句显示出最佳效果。让我更详细地解释一下。
在您的代码中,处理器无论如何都必须执行
foo
。因此 likely
在这里不会有任何效果,因为在推测执行期间不能跳过任何代码路径。函数必须被执行。假设您将之前 foo
的结果保存在变量中,代码如下所示:
int x = foo();
if (likely(x))
{
puts("Azaza");
}
在这种情况下,
likely(x)
可能不会产生任何影响,因为处理器刚刚计算了 x 并且该值很可能缓存在 L1 中(除非它在此时被中断)。
现在假设您有一个全局变量
volatile int c = 15
,我们更改您的代码:
if (likely(b == 15))
{
puts("Azaza");
} else {
puts("Bzaza");
}
当我们执行代码并且第一次访问
b
时,它不会在缓存中,处理器必须从内存中获取它。这会花费数百个 CPU 周期,并且 CPU 不会停止运行,而是在不知道 b 值的情况下开始推测性地执行代码。在这种情况下,可能的关键字表明我们应该转到第一个分支。请注意,我们当时不会执行这些指令。现代 x86 处理器可以推测性地执行多达 400 个微操作,并且只有在预测成立时才会提交结果。
因此,为了回答您的问题,我会将
likely/unlikely
关键字放在 a < b
周围。