【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

前言

说来也是巧最近在看 Dubbo 源码,而后发现了一处很奇怪的代码,恰好和这个 switch 和 if else 有关!程序员

让咱们来看一下这段代码,它属于 ChannelEventRunnable,这个 runnable 是 Dubbo IO 线程建立,将此任务扔到业务线程池中处理,整理了一份近期Java+最多见的+200+学习笔记+面试题汇总,但愿对你们的吸取有帮助。面试

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

因而就开始了一波探险之旅!ide

原来是 CPU 分支预测

遇到问题固然是问搜索引擎了,通常而言我会同时搜索各大引擎,咱这也不说谁比谁好,反正有些时候度娘仍是不错的,好比此次搜索度娘给的结果比较靠前,google 比较靠后。性能

通常搜索东西我都喜欢先在官网上搜,找不到了再放开搜,因此先这么搜 site:xxx.com key。学习

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

你看这就有了,完美啊!测试

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

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 官网的例子名字打错了,我一开始没发现后来校对文章才发现)。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

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

if vs switch

执行效率

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

执行结果

来看一下执行的结果如何:

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

好家伙,我跑了好几回,这全 if 的比 if + switch 强很多啊,因此是否是源码应该全改为 if else 的方式,你看这吞吐量又高,还不会像如今一下 if 一下又 switch 有点不三不四的样子。

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

反编译 if 和 switch

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

switch 的反编译

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

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

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

还有当 switch 里面的值不连续可是差距比较小的时候,仍是会生成 tableswtich 不过填充了一些值,好比这个例子我 switch 里面的值就 一、三、五、七、9,它自动填充了二、四、六、8 都指到 default 所跳的行。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

if 的反编译

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

因此从生成的字节码角度来看 switch 效率应该是大于 if 的,可是从测试结果的角度来看 if 的效率又是高于 switch 的,不管是随机生成 state,仍是 99.99% 都是同一个 state 的状况下。

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

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

好像距离被拉近了,我看有戏,因而我背了波 26 个字母,实不相瞒仍是唱着打的字母。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

扩充了分支的数量后又进行了一波测试,此次 swtich 争气了,终于比 if 强了。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

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

小结一下测试结果

对比了这么多咱们来小结一下。

对于热点分支

首先对于热点分支将其从 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),再来看下维基百科上的一个图。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

固然步骤实际可能更多,反正就是这个意思须要经历这么多步,因此说一次执行能够分红不少步骤,那么这么多步骤就能够并行,来提高处理的效率。

指令流水线

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

就像咱们工厂的流水线同样,我这个奥特曼的脚拼上去了立刻拼下一个奥特曼的脚,我可不会等上一个奥特曼的都组装完了再组装下一个奥特曼。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

固然也没有这么死板,不必定就是顺序执行,有些指令在等待然后面的指令其实不依赖前面的结果,因此能够提早执行,这种叫乱序执行

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

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

那预测错了怎么办?这和我们人生不同,它能够把以前执行的结果全抛了而后再来一遍,可是也有影响,也就是流水线越深,错的越多浪费的也就越多,错误的预测延迟是10至20个时钟周期之间,因此仍是有反作用的。

简单的说就是经过分支预测器来预测未来要跳转执行的那些指令,而后预执行,这样到真正须要它的时候能够直接拿到结果了,提高了效率。

分支预测

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

静态预测

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

动态预测

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

随机预测

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

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

以后又有个名为 BranchScope 的***,也是利用预测执行,因此说每当一个新的玩意出来老是会带来利弊。

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

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

为何处理有序数组要比非有序数组快?

这个问题在那篇博客开头就被提出来了,很明显这也是和分支预测有关系,既然看到了索性就再分析一波,大伙能够在脑海里先回答一下这个问题,毕竟我们都知道答案了,看看思路清晰不。

就是下面这段代码,数组排序了以后循环的更快。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

而后各路大神就蹦出来了,咱们来看一下首赞的大佬怎么说的。

一开口就是,直击要害。

You are a victim of branch prediction fail.

紧接着就上图了,一看就是老司机。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

他说让咱们回到 19世纪,一个没法远距离交流且无线电还未普及的时候,若是是你这个铁路交叉口的扳道工,当火车快来的时候,你如何得知该扳哪一边?

火车停车再重启的消耗是很大的,每次到分叉口都停车,而后你问他,哥们去哪啊,而后扳了道,再重启就很耗时,怎么办?猜!

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

猜对了火车就不用停,继续开。猜错了就停车而后倒车而后换道再开。

因此就看猜的准不许了!搏一搏单车变摩托。

而后大佬又指出了关键代码对应的汇编代码,也就是跳转指令了,这对应的就是火车的岔口,该选条路了。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

后面我就不分析了,大伙儿应该都知道了,排完序的数组执行到值大于 128 的以后确定所有大于128了,因此每次分支预测的结果都是对了!因此执行的效率很高。

而没排序的数组是乱序的,因此不少时候都会预测错误,而预测错误就得指令流水线排空啊,而后再来一遍,这速度固然就慢了。

因此大佬说这个题主你是分支预测错误的受害者。

最终大佬给出的修改方案是咱不用 if 了,惹不起咱还躲不起嘛?直接利用位运算来实现这个功能,具体我就不分析了,给你们看下大佬的建议修改方案。

【揭秘】为何程序员喜欢用大量的if else而偏不用switch!

最后

这篇文章就差很少了,今天就是从 Dubbo 的一段代码开始了探险之旅,分析了波 if 和 switch,从测试结果来看 Dubbo 的此次优化还不够完全,应该所有改为 if else 结构。

而 swtich 从字节码上看是优于 if 的,可是从测试结果来看在分支不少的状况下能显示出优点,通常状况下仍是打不过 if 。

而后也知晓了什么叫指令流水线,这其实就是结合实际了,流水线才够快呀,而后分支预测预执行也是一个提升效率的方法,固然得猜的对,否则分支预测错误的反作用仍是没法忽略的,因此对分支预测器的要求也是很高的。

相关文章
相关标签/搜索