vlambda博客
学习文章列表

dubbo高性能之细节

dubbo为了高性能,采用了很多技术,包括序列化,通信协议等。其实从代码配合cpu层面也有很多,今天介绍一个。

这边if有个怪代码。

dubbo高性能之细节

很大的疑问,竟然有了switch,为啥前面还加一个if, 百思不得其解。

老办法,百度之

百度有一段这么个解释:

然后又因为一个 channel 建立了之后,超过99.9%情况它的 state 都是 ChannelState.RECEIVED,因此就把这个状态给挑出来,这样就能利用 CPU 分支预测机制来提高代码的执行效率。


对于热点分支将其从 switch 提取出来用 if 独立判断,充分利用 CPU 分支预测带来的便利确实优于纯 swtich,从我们的代码测试结果来看,大致吞吐量高了两倍。

在热点分支的情形下改成纯 if 判断而不是 if + swtich的情形下,吞吐量提高的更多。是纯 switch 的 3.3 倍,是 if + switch 的 1.6 倍。

在随机分支的情形下,三者差别不是很大,但是还是纯 if 的情况最优秀。

但是从字节码角度来看其实 switch 的机制效率应该更高的,不论是 O(1) 还是 O(logn),但是从测试结果的角度来说不是的。

在选择条件少的情况下 if 是优于 switch 的,这个我不太清楚为什么,可能是在值较少的情况下查表的消耗相比带来的收益更大一些?有知道的小伙伴可以在文末留言。

在选择条件很多的情况下 switch 是优于 if 的,再多的选择值我就没测了,大伙有兴趣可以自己测测,不过趋势就是这样的。



CPU 分支预测

接下来咱们再来看看这个分支预测到底是怎么弄的,为什么会有分支预测这玩意,不过在谈到分支预测之前需要先介绍下指令流水线(Instruction pipelining),也就是现代微处理器的 pipeline。

CPU 本质就是取指执行,而取指执行我们来看下五大步骤,分别是获取指令(IF)、指令解码(ID)、执行指令(EX)、内存访问(MEM)、写回结果(WB),再来看下维基百科上的一个图。

当然步骤实际可能更多,反正就是这个意思需要经历这么多步,所以说一次执行可以分成很多步骤,那么这么多步骤就可以并行,来提升处理的效率。

所以说指令流水线就是试图用一些指令使处理器的每一部分保持忙碌,方法是将传入的指令分成一系列连续的步骤,由不同的处理器单元执行,不同的指令部分并行处理。

当然也没有这么死板,不一定就是顺序执行,有些指令在等待而后面的指令其实不依赖前面的结果,所以可以提前执行,这种叫乱序执行。

我们再说回我们的分支预测。

这代码就像我们的人生一样总会面临着选择,只有做了选择之后才知道后面的路怎么走呀,但是事实上发现这代码经常走的是同一个选择,于是就想出了一个分支预测器,让它来预测走势,提前执行一路的指令。

那预测错了怎么办?这和咱们人生不一样,它可以把之前执行的结果全抛了然后再来一遍,但是也有影响,也就是流水线越深,错的越多浪费的也就越多,错误的预测延迟是10至20个时钟周期之间,所以还是有副作用的。

简单的说就是通过分支预测器来预测将来要跳转执行的那些指令,然后预执行,这样到真正需要它的时候可以直接拿到结果了,提升了效率。

分支预测又分了很多种预测方式,有静态预测、动态预测、随机预测等等,从维基百科上看有16种。

我简单说下我提到的三种,静态预测就是愣头青,就和蒙英语选择题一样,我管你什么题我都选A,也就是说它会预测一个走势,一往无前,简单粗暴。

动态预测则会根据历史记录来决定预测的方向,比如前面几次选择都是 true ,那我就走 true 要执行的这些指令,如果变了最近几次都是 false ,那我就变成 false 要执行的这些指令,其实也是利用了局部性原理。

随机预测看名字就知道了,这是蒙英语选择题的另一种方式,瞎猜,随机选一个方向直接执行。

还有很多就不一一列举了,各位有兴趣自行去研究,顺便提一下在 2018 年谷歌的零项目和其他研究人员公布了一个名为 Spectre 的灾难性安全漏洞,其可利用 CPU 的分支预测执行泄漏敏感信息,这里就不展开了,文末会附上链接。

之后又有个名为 BranchScope 的攻击,也是利用预测执行,所以说每当一个新的玩意出来总是会带来利弊。

至此我们已经知晓了什么叫指令流水线和分支预测了,也理解了 Dubbo 为什么要这么优化了,但是文章还没有结束,我还想提一提这个 stackoverflow 非常有名的问题,看看这数量。