Dubbo 这波优化好像不够彻底啊?(上)

简介: Dubbo 这波优化好像不够彻底啊?(上)

每个时代,都不会亏待会学习的人。

大家好,我是 yes。

这次本来是打算写一篇 RocketMQ 相关文章的,但是被插队了,我也是没想到的。

说来也是巧最近在看 Dubbo 源码,然后发现了一处很奇怪的代码,于是就有了这篇文章,让我们来看一下这段代码,它属于 ChannelEventRunnable,这个 runnable 是 Dubbo IO 线程创建,将此任务扔到业务线程池中处理。


image.png

看到没,把 state == ChannelState.RECEIVED 拎出来独立一个 if,而其他的 state 还是放在 switch 里面判断。

image.png

我当时脑子里就来回扫描,想想这个到底有什么花头,奈何知识浅薄一脸懵逼。

于是就开始了一波探险之旅!


原来是 CPU 分支预测


遇到问题当然是问搜索引擎了,一般而言我会同时搜索各大引擎,咱这也不说谁比谁好,反正有些时候度娘还是不错的,比如这次搜索度娘给的结果比较靠前,google 较靠后。

一般搜索东西我都喜欢先在官网上搜,找不到了再放开搜,所以先这么搜 site:xxx.com key

image.png

你看这就有了,完美啊!


我们先来看看官网的这篇博客怎么说的,然后再详细地分析一波。


Dubbo 官网的博客


现代 CPU 都支持分支预测 (branch prediction) 和指令流水线 (instruction pipeline),这两个结合可以极大提高 CPU 效率。对于像简单的 if 跳转,CPU 是可以比较好地做分支预测的。但是对于 switch 跳转,CPU 则没有太多的办法。 switch 本质上是根据索引,从地址数组里取地址再跳转。


也就是说 if 是跳转指令,如果是简单的跳转指令的话 CPU 可以利用分支预测来预执行指令,而 switch 是要先根据值去一个类似数组结构找到对应的地址,然后再进行跳转,这样的话 CPU 预测就帮不上忙了。


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


并且还给出了 Benchmark 的代码,就是通过随机生成 100W 个 state,并且 99.99% 是  ChannelState.RECEIVED,然后按照以下两种方式来比一比(这 benchSwitch 官网的例子名字打错了,我一开始没发现后来校对文章才发现)。




虽然博客也给出了它的对比结果,但是我还是本地来跑一下看看结果如何,其实 JMH 不推荐在 ide 里面跑,但是我懒,直接 idea 里面跑了。

image.png

从结果来看确实通过 if 独立出来代码的执行效率更高(注意这里测的是吞吐),博客还提出了这种技巧可以放在性能要求严格的地方,也就是一般情况下没必要这样特殊做。

至此我们已经知道了这个结论是对的,不过我们还需要深入分析一波,首先得看看  if 和 switch 的执行方式到底差别在哪里,然后再看看 CPU 分支预测和指令流水线的到底是干啥的,为什么会有这两个东西?


if vs switch


我们先简单来个小 demo 看看 if 和 switch 的执行效率,其实就是添加一个全部是 if else 控制的代码, switch 和 if + switch 的不动,看看它们之间对比效率如何(此时还是 RECEIVED 超过99.9%)。


image.png


好家伙,我跑了好几次,这全 if 的比 if + switch 强不少啊,所以是不是源码应该全改成 if else 的方式,你看这吞吐量又高,还不会像现在一下 if 一下又 switch 有点不伦不类的样子。


我又把 state 生成的值改成随机的,再来跑一下看看结果如何:


image.png

我跑了多次还是 if 的吞吐量都是最高的,怎么整这个全 if 的都是最棒滴。


反编译 if 和 switch


在我的印象里这个 switch 应该是优于 if 的,不考虑 CPU 分支预测的话,当从字节码角度来说是这样的,我们来看看各自生成的字节码。


先看一下 switch 的反编译,就截取了关键部分。

image.png

也就是说 switch 生成了一个 tableswitch,上面的 getstatic 拿到值之后可以根据索引直接查这个 table,然后跳转到对应的行执行即可,也就是时间复杂度是 O(1)。


比如值是 1 那么直接跳到执行 64 行,如果是 4 就直接跳到 100 行。


关于 switch 还有一些小细节,当 swtich 内的值不连续且差距很大的时候,生成的是 lookupswitch,按网上的说法是二分法进行查询(我没去验证过),时间复杂度是 O(logn),不是根据索引直接能找到了,我看生成的 lookup 的样子应该就是二分了,因为按值大小排序了。


image.png

让我们再来看看 if 的反编译结果

image.png

可以看到 if 是每次都会取出变量和条件进行比较,而 switch 则是取一次变量之后查表直接跳到正确的行,从这方面来看 switch 的效率应该是优于 if 的。当然如果 if 在第一次判断就过了的话也就直接 goto 了,不会再执行下面的哪些判断了。


所以从生成的字节码角度来看 switch 效率应该是大于 if 的,但是从测试结果的角度来看 if 的效率又是高于 switch 的,不论是随机生成 state,还是 99.99% 都是同一个 state 的情况下。


首先 CPU 分支预测的优化是肯定的,那关于随机情况下 if 还是优于 switch 的话这我就有点不太确定为什么了,可能是 JIT 做了什么优化操作,或者是随机情况下分支预测成功带来的效益大于预测失败的情形?


难道是我枚举值太少了体现不出 switch 的效果?不过在随机情况下 switch 也不应该弱于 if 啊,我又加了 7 个枚举值,一共 12 个值又测试了一遍,结果如下:


image.png

题外话:

我看网上也有对比 if 和 switch 的,它们对比出来的结果是 switch 优于 if,首先 jmh 就没写对,定义一个常量来测试 if 和 switch,并且测试方法的 result 写了没有消费,这代码也不知道会被 JIT 优化成啥样了,写了几十行,可能直接优化成 return 某个值了。





相关文章
|
XML Dubbo Java
Dubbo 3 Spring相关优化
Spring Context Initialization首先,我们先来看一下Spring context初始化主要流程,如下图所示: 相关代码:org.springframework.context.support.AbstractApplicationContext#refresh()简单描述一下每个步骤包含的内容:创建BeanFactory:读取加载XML/注解定义的BeanDefiniti
833 3
Dubbo 3 Spring相关优化
|
负载均衡 Dubbo Cloud Native
Dubbo 2.7.5在线程模型上的优化(上)
Dubbo 2.7.5在线程模型上的优化(上)
448 0
|
Dubbo 应用服务中间件
Dubbo 这波优化好像不够彻底啊?(下)
Dubbo 这波优化好像不够彻底啊?(下)
Dubbo 这波优化好像不够彻底啊?(下)
|
Dubbo 应用服务中间件
Dubbo 这波优化好像不够彻底啊?(中)
Dubbo 这波优化好像不够彻底啊?(中)
Dubbo 这波优化好像不够彻底啊?(中)
|
自然语言处理 Dubbo Cloud Native
Dubbo 2.7.6在线程模型上的优化(下)
Dubbo 2.7.6在线程模型上的优化(下)
278 0
|
Kubernetes Dubbo Cloud Native
开发者社区精选直播合集(二十九)| Apache Dubbo 优化探索之旅
Dubbo 3.0 作为三位一体架构的首推方案,在集团内被寄予了厚望。它完美融合了内部 HSF 的特性,天然拥有高性能、高可用的核心能力,我们期望用它来解决内部落地问题,做到技术栈统一。
开发者社区精选直播合集(二十九)|  Apache Dubbo 优化探索之旅
|
监控 Dubbo 搜索推荐
异构(兼容dubbo)SOA系统架构(.net)优化升级
原文:异构(兼容dubbo)SOA系统架构(.net)优化升级 前面一片文章已经提到我司的异构(兼容dubbo)SOA系统架构,解决了不少技术痛点,也还算比较完善,也顺利推广开来。 但作为项目的开发者,自己产品的问题心里是清楚的,离自己满意还是有不小的距离。
1093 0
|
5月前
|
负载均衡 Dubbo 应用服务中间件
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
微服务技术系列教程(31) - Dubbo-原理及负载均衡分析
56 0
|
5月前
|
Dubbo Java 应用服务中间件
微服务技术系列教程(30) - Dubbo-SpringCloud与Dubbo区别
微服务技术系列教程(30) - Dubbo-SpringCloud与Dubbo区别
47 0