switch 的性能提升了 3 倍,我只用了这一招!
這是我的第 190?期分享
作者 | 王磊
來源 |?Java中文社群(ID:javacn666)?
分享?| Java中文社群(ID:javacn666)
上一篇《if快還是switch快?解密switch背后的秘密》我們測試了 if 和 switch 的性能,得出了要盡量使用 switch 的結(jié)論,因?yàn)樗男时?if 高很多,具體原因點(diǎn)擊上文連接查看。
既然 switch 如此有魅力,那么有沒有更好的方法,讓 switch 變得更快一些呢?
答案是有的,不然本文就不會(huì)誕生了不是?
在上篇 if 和 switch 性能對比的文章中有讀者問到:String 類型的 switch 性能是否也比 if 高?先說答案,String 類型的條件判斷 switch 的性能依舊比 if 好。
口說無憑,先舉個(gè)????,測試代碼如下:
import?org.openjdk.jmh.annotations.*; import?org.openjdk.jmh.infra.Blackhole; import?org.openjdk.jmh.runner.Runner; import?org.openjdk.jmh.runner.RunnerException; import?org.openjdk.jmh.runner.options.Options; import?org.openjdk.jmh.runner.options.OptionsBuilder;import?java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)?//?測試完成時(shí)間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預(yù)熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?3,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個(gè)線程 @State(Scope.Thread)?//?每個(gè)測試線程一個(gè)實(shí)例 public?class?SwitchOptimizeByStringTest?{static?String?_STR?=?"Java中文社群";public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動(dòng)基準(zhǔn)測試Options?opt?=?new?OptionsBuilder().include(SwitchOptimizeByStringTest.class.getSimpleName())?//?要導(dǎo)入的測試類.build();new?Runner(opt).run();?//?執(zhí)行測試}@Benchmarkpublic?void?switchTest(Blackhole?blackhole)?{String?s1;switch?(_STR)?{case?"java":s1?=?"java";break;case?"mysql":s1?=?"mysql";break;case?"oracle":s1?=?"oracle";break;case?"redis":s1?=?"redis";break;case?"mq":s1?=?"mq";break;case?"kafka":s1?=?"kafka";break;case?"rabbitmq":s1?=?"rabbitmq";break;default:s1?=?"default";break;}//?為了避免?JIT?忽略未被使用的結(jié)果計(jì)算,可以使用?Blackhole#consume?來保證方法被正常執(zhí)行blackhole.consume(s1);}@Benchmarkpublic?void?ifTest(Blackhole?blackhole)?{String?s1;if?("java".equals(_STR))?{s1?=?"java";}?else?if?("mysql".equals(_STR))?{s1?=?"mysql";}?else?if?("oracle".equals(_STR))?{s1?=?"oracle";}?else?if?("redis".equals(_STR))?{s1?=?"redis";}?else?if?("mq".equals(_STR))?{s1?=?"mq";}?else?if?("kafka".equals(_STR))?{s1?=?"kafka";}?else?if?("rabbitmq".equals(_STR))?{s1?=?"rabbitmq";}?else?{s1?=?"default";}//?為了避免?JIT?忽略未被使用的結(jié)果計(jì)算,可以使用?Blackhole#consume?來保證方法被正常執(zhí)行blackhole.consume(s1);} }特殊說明:本文使用的是 Oracle 官方提供的性能測試工具 JMH(Java Microbenchmark Harness,JAVA 微基準(zhǔn)測試套件)進(jìn)行測試的。
以上代碼測試的結(jié)果如下:
從 Score 列(平均完成時(shí)間)可以看出 switch 的性能依舊比 if 的性能要高。
備注:本文的測試環(huán)境為:JDK 1.8 / Mac mini (2018) / Idea 2020.1
switch 性能優(yōu)化
我們知道在 JDK 1.7 之前 switch 是不支持 String 的,實(shí)際上 switch 只支持 int 類型。
在 JDK 1.7 中的 String 類型,其實(shí)在編譯的時(shí)候會(huì)使用 hashCode 來作為 switch 的實(shí)際值,以上 switch 判斷字符串的代碼,編譯為字節(jié)碼實(shí)際結(jié)果如下:
public?static?void?switchTest()?{String?var1?=?_STR;byte?var2?=?-1;switch(var1.hashCode())?{case?-1008861826:if?(var1.equals("oracle"))?{var2?=?2;}break;case?-95168706:if?(var1.equals("rabbitmq"))?{var2?=?6;}break;case?3492:if?(var1.equals("mq"))?{var2?=?4;}break;case?3254818:if?(var1.equals("java"))?{var2?=?0;}break;case?101807910:if?(var1.equals("kafka"))?{var2?=?5;}break;case?104382626:if?(var1.equals("mysql"))?{var2?=?1;}break;case?108389755:if?(var1.equals("redis"))?{var2?=?3;}}//?忽略其他代碼... }知道了 switch 實(shí)現(xiàn)的本質(zhì),那么優(yōu)化就變得比較簡單了。
從以上的字節(jié)碼可以看出,如果要優(yōu)化 switch 只需要把 String 類型變成 int 類型就可以了,這樣就剩了每個(gè) case 中進(jìn)行 if 判斷的性能消耗,最終的優(yōu)化代碼如下:
public?void?switchHashCodeTest()?{String?s1;switch?(_STR.hashCode())?{case?3254818:s1?=?"java";break;case?104382626:s1?=?"mysql";break;case?-1008861826:s1?=?"oracle";break;case?108389755:s1?=?"redis";break;case?3492:s1?=?"mq";break;case?101807910:s1?=?"kafka";break;case?-95168706:s1?=?"rabbitmq";break;default:s1?=?"default";break;} }此時(shí)我們使用 JMH 進(jìn)行實(shí)際的測試,測試代碼如下:
import?org.openjdk.jmh.annotations.*; import?org.openjdk.jmh.infra.Blackhole; import?org.openjdk.jmh.runner.Runner; import?org.openjdk.jmh.runner.RunnerException; import?org.openjdk.jmh.runner.options.Options; import?org.openjdk.jmh.runner.options.OptionsBuilder;import?java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)?//?測試完成時(shí)間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預(yù)熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?3,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個(gè)線程 @State(Scope.Thread)?//?每個(gè)測試線程一個(gè)實(shí)例 public?class?SwitchOptimizeByStringTest?{static?String?_STR?=?"Java中文社群";public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動(dòng)基準(zhǔn)測試Options?opt?=?new?OptionsBuilder().include(SwitchOptimizeByStringTest.class.getSimpleName())?//?要導(dǎo)入的測試類.build();new?Runner(opt).run();?//?執(zhí)行測試}@Benchmarkpublic?void?switchHashCodeTest(Blackhole?blackhole)?{String?s1;switch?(_STR.hashCode())?{case?3254818:s1?=?"java";break;case?104382626:s1?=?"mysql";break;case?-1008861826:s1?=?"oracle";break;case?108389755:s1?=?"redis";break;case?3492:s1?=?"mq";break;case?101807910:s1?=?"kafka";break;case?-95168706:s1?=?"rabbitmq";break;default:s1?=?"default";break;}//?為了避免?JIT?忽略未被使用的結(jié)果計(jì)算,可以使用?Blackhole#consume?來保證方法被正常執(zhí)行blackhole.consume(s1);}@Benchmarkpublic?void?switchTest(Blackhole?blackhole)?{String?s1;switch?(_STR)?{case?"java":s1?=?"java";break;case?"mysql":s1?=?"mysql";break;case?"oracle":s1?=?"oracle";break;case?"redis":s1?=?"redis";break;case?"mq":s1?=?"mq";break;case?"kafka":s1?=?"kafka";break;case?"rabbitmq":s1?=?"rabbitmq";break;default:s1?=?"default";break;}//?為了避免?JIT?忽略未被使用的結(jié)果計(jì)算,可以使用?Blackhole#consume?來保證方法被正常執(zhí)行blackhole.consume(s1);}@Benchmarkpublic?void?ifTest(Blackhole?blackhole)?{String?s1;if?("java".equals(_STR))?{s1?=?"java";}?else?if?("mysql".equals(_STR))?{s1?=?"mysql";}?else?if?("oracle".equals(_STR))?{s1?=?"oracle";}?else?if?("redis".equals(_STR))?{s1?=?"redis";}?else?if?("mq".equals(_STR))?{s1?=?"mq";}?else?if?("kafka".equals(_STR))?{s1?=?"kafka";}?else?if?("rabbitmq".equals(_STR))?{s1?=?"rabbitmq";}?else?{s1?=?"default";}//?為了避免?JIT?忽略未被使用的結(jié)果計(jì)算,可以使用?Blackhole#consume?來保證方法被正常執(zhí)行blackhole.consume(s1);} }以上代碼測試的結(jié)果如下:
從以上結(jié)果可以看出,String 類型的 switch 判斷,經(jīng)過優(yōu)化之后,性能提升了 2.4 倍,可謂效果顯著。
注意事項(xiàng)
以上的 switch 優(yōu)化是基于 String 類型的,同時(shí)我們需要注意 hashCode 重復(fù)的問題,例如對于字符串“Aa”和“BB”來說,他們的 hashCode 都是 2112,因此在優(yōu)化是需要注意此類問題,也就是說我們使用 hashCode 時(shí),必須保證判斷添加的值是已知的,并且最好不要出現(xiàn) hashCode 重復(fù)的問題,如果出現(xiàn)此類問題,我們的解決方案是在 case 中進(jìn)行判斷并賦值。
其他優(yōu)化手段
我們本文重點(diǎn)討論的是 switch 性能優(yōu)化的方案,當(dāng)然如果處于性能考慮,我們還可以使用更加高效的替代方案,例如集合或者是枚舉,詳見我的另一篇文章《9個(gè)小技巧讓你的 if else看起來更優(yōu)雅》。
總結(jié)
通過本文我們知道?switch 本質(zhì)上只支持 int 類型的條件判斷,即使是 JDK 1.7 中的 String 類型,最終編譯的時(shí)候還是會(huì)被轉(zhuǎn)化為 hashCode(int)進(jìn)行判斷。但因?yàn)榫幾g成字節(jié)碼后會(huì)在 case 中使用 if equals 進(jìn)行比較,所以性能并不算太高(只比 if 高一點(diǎn)點(diǎn)),因此我們可以直接把 String 轉(zhuǎn)化成 int 類型進(jìn)行比較,從而避免在 case 中進(jìn)行 if equals 判斷的性能消耗,這樣就大大的提升 switch 的性能,但需要注意的是,有些 key 值的 hashCode 是相同的,因此在優(yōu)化時(shí)需要提前規(guī)避。
最后的話
原創(chuàng)不易,如果覺得本文對你有用,請隨手點(diǎn)擊一個(gè)「贊」,這是對作者最大的支持與鼓勵(lì),謝謝你。
if快還是switch快?解密switch背后的秘密
HashMap 的 7 種遍歷方式與性能分析!「修正篇」
關(guān)注公眾號發(fā)送”進(jìn)群“,老王拉你進(jìn)讀者群。
總結(jié)
以上是生活随笔為你收集整理的switch 的性能提升了 3 倍,我只用了这一招!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linq to js使用汇总
- 下一篇: 2万字,看完这篇才敢说自己真的懂线程池!