CPU的分支预测

我们在Swoole源码里面会看到很多的likelyunlikely宏,例如在创建协程的时候,有如下代码:

1
2
3
4
5
6
7
8
9
long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params);
if (sw_likely(cid > 0))
{
RETURN_LONG(cid);
}
else
{
RETURN_FALSE;
}

这里,Swoole调用PHPCoroutine::create来创建协程,并且返回了协程的id。然后,接着是对cid使用了宏likely。这个用法是为了提升CPU指令缓存的命中率。

CPU缓存是几块离CPU近的存储空间。CPU通常分为三级缓存(不是代表只有三块缓存,而是等级有三类)。我们可以通过一下命令来查看CPU缓存的大小:

1
2
3
4
5
6
[root@bceb11389603 test]# lscpu | grep cache
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 4096K
[root@bceb11389603 test]#

可以看出,我的机器上有四块CPU缓存,其中有两块L1缓存,大小是32K;一块L2缓存,大小是256KB;一块L3缓存,大小是4096KB

我们发现,L3缓存要比L1L2级缓存大很多,因为现在的CPU都是多核的,每个核都有自己的L1L2级缓存,但L3级缓存却是同一颗CPU上所有核心共享的。程序执行时,会先将内存中的数据载入到共享的L3级缓存中,再进入每颗核心独有的L2级缓存,最后进入最快的L1级缓存,最后才会被CPU的核使用。

那么为什么有两个L1级缓存呢?因为CPU核会对指令与数据进行区分。指令会放在L1指令缓存中,而指令所需要的数据放在L1数据缓存中。

所以,我们前面所说的likelyunlikely实际上就是为了提升指令缓存的命中率。而likelyunlikely宏是编译器为我们提供的显式预测分支概率的工具。实际上,CPU自身的条件预测已经非常准了,仅当我们确定CPU条件预测不准,且我们能够知晓实际概率时,才需要加入这两个宏。

如果我们需要查看指令缓存的命中率,我们可以通过perf这个工具来查看