扯什么trycatch性能问题?
你看着这鬼代码,竟然在for循环里面搞了个trycatch,不知道trycatch有性能损耗吗?老陈煞有其事地指着屏幕里的代码:for(inti0;i5000;i){try{dosth}catch(Exceptione){e。printStackTrace();}}复制代码
我探过头去看了眼代码,那老陈你觉得该怎么改?
当然是把trycatch提到外面啊!老陈脑子都不转一下,脱口而出。
你是不是傻?且不说性能,这代码的目的明显是让循环内部单次调用出错不影响循环的运行,你其到外面业务逻辑不就变了吗!
老陈挠了挠他的地中海,好像也是啊!
回过头来,catch整个for循环和在循环内部catch,在不出错的情况下,其实性能差不多。我喝一口咖啡不经意地提到,准备在老陈前面秀一下。
啥意思?老陈有点懵地看着我,trycatch是有性能损耗的,我可是看过网上资料的!
果然,老陈上钩了,我二话不说直接打开idea,一顿操作敲了以下代码:publicclassTryCatchTest{Benchmarkpublicvoidtryfor(Blackholeblackhole){try{for(inti0;i5000;i){blackhole。consume(i);}}catch(Exceptione){e。printStackTrace();}}Benchmarkpublicvoidfortry(Blackholeblackhole){for(inti0;i5000;i){try{blackhole。consume(i);}catch(Exceptione){e。printStackTrace();}}}}复制代码
BB不如showcode,看到没,老陈,我把trycatch从for循环里面提出来跟在for循环里面做个对比跑一下,你猜猜两个差多少?
切,肯定tryfor性能好,想都不用想,不是的话我倒立洗头!老陈信誓旦旦道。
我懒得跟他BB,直接开始了benchmark,跑的结果如下:
可以看到,两者的性能(数字越大越好)其实差不多:fortry:86,261(10035914098)114,457(10035914098)tryfor:95,961(1032167255)110,471(1032167255)
我再调小(一般业务场景for循环次数都不会很多)下for循环的次数为1000,结果也是差不多:
老陈一看傻了:说好的性能影响呢?怎么没了?
我直接一个javap,让老陈看看,其实两个实现在字节码层面没啥区别:
tryfor的字节码
异常表记录的是020行,如果这些行里面的代码出现问题,直接跳到23行处理。
fortry的字节码
差别也就是异常表的范围小点,包的是914行,其它跟tryfor都差不多。
所以从字节码层面来看,没抛错两者的执行效率其实没啥差别。
那为什么网上流传着trycatch会有性能问题的说法啊?老陈觉得非常奇怪。
这个说法确实有,在《EffectiveJava》这本书里就提到了trycatch性能问题:
并且还有下面一段话:
正所谓听话不能听一半,以前读书时候最怕的就是一知半解,因为完全理解选择题能选对,完全不懂蒙可能蒙对,一知半解必定选到错误的选项!
《EffectiveJava》书中说的其实是不要用trycatch来代替正常的代码,书中的举例了正常的for循环肯定这样实现:
但有个卧龙偏偏不这样实现,要通过trycatch拐着弯来实现循环:
这操作我只能说有点逆天,这两个实现的对比就有性能损耗了。
我们直接再跑下有trycatch的代码和没trycatch的for循环区别,代码如下:
结果如下:
差不多,直接看前面的分数对比,没有trycatch的性能确实好些,这也和书中说的trycatch会影响JVM一些特定的优化说法吻合,但是具体没有说影响哪些优化,我猜测可能是指令重排之类的。性能评测
话不多说,我们直接来开始今天的测试,本文我们依旧使用Oracle官方提供的JMH(JavaMicrobenchmarkHarness,JAVA微基准测试套件)来进行测试。
首先在pom。xml文件中添加JMH框架,配置如下:!https:mvnrepository。comartifactorg。openjdk。jmhjmhcoredependencygroupIdorg。openjdk。jmhgroupIdjmhcoreartifactIdversion{version}versiondependency
完整测试代码如下:importorg。openjdk。jmh。annotations。;importorg。openjdk。jmh。runner。Runner;importorg。openjdk。jmh。runner。RunnerException;importorg。openjdk。jmh。runner。options。Options;importorg。openjdk。jmh。runner。options。OptionsBuilder;importjava。util。concurrent。TimeUnit;trycatch性能测试BenchmarkMode(Mode。AverageTime)测试完成时间OutputTimeUnit(TimeUnit。NANOSECONDS)Warmup(iterations1,time1,timeUnitTimeUnit。SECONDS)预热1轮,每次1sMeasurement(iterations5,time5,timeUnitTimeUnit。SECONDS)测试5轮,每次3sFork(1)fork1个线程State(Scope。Benchmark)Threads(100)publicclassTryCatchPerformanceTest{privatestaticfinalintforSize1000;循环次数publicstaticvoidmain(String〔〕args)throwsRunnerException{启动基准测试OptionsoptnewOptionsBuilder()。include(TryCatchPerformanceTest。class。getSimpleName())要导入的测试类。build();newRunner(opt)。run();执行测试}BenchmarkpublicintinnerForeach(){intcount0;for(inti0;iforSize;i){try{if(iforSize){thrownewException(newException);}count;}catch(Exceptione){e。printStackTrace();}}returncount;}BenchmarkpublicintouterForeach(){intcount0;try{for(inti0;iforSize;i){if(iforSize){thrownewException(newException);}count;}}catch(Exceptione){e。printStackTrace();}returncount;}}
以上代码的测试结果为:
从以上结果可以看出,程序在循环1000次的情况下,单次平均执行时间为:循环内包含trycatch的平均执行时间是635纳秒75纳秒,也就是635纳秒上下误差是75纳秒;循环外包含trycatch的平均执行时间是630纳秒,上下误差38纳秒。
也就是说,在没有发生异常的情况下,除去误差值,我们得到的结论是:trycatch无论是在for循环内还是for循环外,它们的性能相同,几乎没有任何差别。
trycatch的本质
要理解trycatch的性能问题,必须从它的字节码开始分析,只有这样我能才能知道trycatch的本质到底是什么,以及它是如何执行的。
此时我们写一个最简单的trycatch代码:publicclassAppTest{publicstaticvoidmain(String〔〕args){try{intcount0;thrownewException(newException);}catch(Exceptione){e。printStackTrace();}}}
然后使用javac生成字节码之后,再使用javapcAppTest的命令来查看字节码文件:javapcAppTest警告:二进制文件AppTest包含com。example。AppTestCompiledfromAppTest。javapublicclasscom。example。AppTest{publiccom。example。AppTest();Code:0:aload01:invokespecial1MethodjavalangObject。init:()V4:returnpublicstaticvoidmain(java。lang。String〔〕);Code:0:iconst01:istore12:new2classjavalangException5:dup6:ldc3StringnewException8:invokespecial4MethodjavalangException。init:(LjavalangString;)V11:athrow12:astore113:aload114:invokevirtual5MethodjavalangException。printStackTrace:()V17:returnExceptiontable:fromtotargettype01212ClassjavalangException}
从以上字节码中可以看到有一个异常表:Exceptiontable:fromtotargettype01212ClassjavalangException
参数说明:from:表示trycatch的开始地址;to:表示trycatch的结束地址;target:表示异常的处理起始位;type:表示异常类名称。
从字节码指令可以看出,当代码运行时出错时,会先判断出错数据是否在from到to的范围内,如果是则从target标志位往下执行,如果没有出错,直接goto到return。也就是说,如果代码不出错的话,性能几乎是不受影响的,和正常的代码的执行逻辑是一样的。
业务情况分析
虽然trycatch在循环体内还是循环体外的性能是类似的,但是它们所代码的业务含义却完全不同,例如以下代码:publicclassAppTest{publicstaticvoidmain(String〔〕args){System。out。println(循环内的执行结果:innerForeach());System。out。println(循环外的执行结果:outerForeach());}方法一publicstaticintinnerForeach(){intcount0;for(inti0;i6;i){try{if(i3){thrownewException(newException);}count;}catch(Exceptione){e。printStackTrace();}}returncount;}方法二publicstaticintouterForeach(){intcount0;try{for(inti0;i6;i){if(i3){thrownewException(newException);}count;}}catch(Exceptione){e。printStackTrace();}returncount;}}
以上程序的执行结果为:
java。lang。Exception:newException
atcom。example。AppTest。innerForeach(AppTest。java:15)
atcom。example。AppTest。main(AppTest。java:5)
java。lang。Exception:newException
atcom。example。AppTest。outerForeach(AppTest。java:31)
atcom。example。AppTest。main(AppTest。java:6)
循环内的执行结果:5
循环外的执行结果:3
可以看出在循环体内的trycatch在发生异常之后,可以继续执行循环;而循环外的trycatch在发生异常之后会终止循环。
因此我们在决定trycatch究竟是应该放在循环内还是循环外,不取决于性能(因为性能几乎相同),而是应该取决于具体的业务场景。
例如我们需要处理一批数据,而无论这组数据中有哪一个数据有问题,都不能影响其他组的正常执行,此时我们可以把trycatch放置在循环体内;而当我们需要计算一组数据的合计值时,只要有一组数据有误,我们就需要终止执行,并抛出异常,此时我们需要将trycatch放置在循环体外来执行。
总结
本文我们测试了trycatch放在循环体内和循环体外的性能,发现二者在循环很多次的情况下性能几乎是一致的。然后我们通过字节码分析,发现只有当发生异常时,才会对比异常表进行异常处理,而正常情况下则可以忽略trycatch的执行。但在循环体内还是循环体外使用trycatch,对于程序的执行结果来说是完全不同的,因此我们应该从实际的业务出发,来决定到trycatch应该存放的位置,而非性能考虑。
好了,我再总结下有关trycatch性能问题说法:trycatch相比较没trycatch,确实有一定的性能影响,但是旨在不推荐我们用trycatch来代替正常能不用trycatch的实现,而不是不让用trycatch。for循环内用trycatch和用trycatch包裹整个for循环性能差不多,但是其实两者本质上是业务处理方式的不同,跟性能扯不上关系,关键看你的业务流程处理。虽然知道trycatch会有性能影响,但是业务上不需要避讳其使用,业务实现优先(只要不是书中举例的那种逆天代码就行),非特殊情况下性能都是其次,有意识地避免大范围的trycatch,只catch需要的部分即可(没把握全catch也行,代码安全执行第一)。
好了,老陈你懂了没?
行啊yes,BB是一套一套的,走请你喝燕麦拿铁!老陈一把拉起我,我直接一个挣脱,少来,我刚喝过咖啡,你那个倒立洗头,赶紧的!我立马意识到老陈想岔开话题。
洗洗洗,我们先喝个咖啡,晚上回去给你洗!
晚上22点,老陈发来一张图片:
你别说,这头发至少比三毛多。
有关于酒桌上的规矩以下是品学网小编为大家整理的有关于酒桌上的规矩,供大家参考!(一)如果自己真不能喝,就别开第一口,端着饭碗夹了菜一边吃着去。(二)如果确信自己要喝,就别装磨叽,接下……
食堂员工工作细则食堂员工是否严格遵守工作细则关乎食品健康问题,那么,以下是品学网小编给大家整理收集的食堂员工工作细则,供大家阅读参考。食堂员工工作细则11、工作衣帽不整齐,不干净一次扣1……
五年级语文教学总结范文今学期我仍然担任四年班语文教学。由于教学经验尚浅。因此,我对教学工作不敢怠慢,认真学习,深入研究教法,虚心向前辈学习。经过一个学期的努力,获取了很多宝贵的教学经验。以下是我在本……
回隆二中上学期开学工作汇报新学期,为确保各项工作顺利开展,我校结合实际情况,扎实有效地抓开学工作。现将学校开学情况汇报如下:一、开学准备工作情况1、正月11日学校召开校务会、行政会、全体教师……
催眠大师相关的经典台词催眠大师经典台词,名言网为给整理编辑,该片将在2014年4月29日上映,我们会为您关注该片的经典台词,及时为您发布。这是由名言为你搜集整理,名言网汇集和分享经典语录、经典台词。……
NASA我们的宇宙正在发生奇怪的事情宇宙膨胀示意图。(ShutterStock)美国国家航空航天局(NASA)说,我们的宇宙正在发生一些奇怪的事情,其膨胀速度远远超出预期。科学家们一直在研究哈勃太空望……
有走下坡路嫌疑的NBA巨星是的,没人一直处于巅峰,巨星也不例外!是时候让位给众小弟了哈登33岁本赛季22。6810,投篮命中率41。6,三分球命中率33。5,这数据,以及能力在NBA还算可以,但与……
久泡的木耳竟然有毒看到这则消息,相信你不寒而栗的同时也会产生疑问,木耳是很常见的食物,基本上家家户户都在吃,它不仅口感好,还富含营养,当然它的做法也是多种多样的,凉拌、炒、涮火锅等等。没想……
2022春夏时尚,怎么错过这一抹梦幻贝母?都说潮流趋势是个圈,但万变不离其宗!特别是在如今快节奏的生活之下,让很多时尚设计师开始返璞归真,从原生自然之中汲取灵感,呈现出令人惊艳的美!而在这其中,贝母色可谓是经久不衰,特……
让机器活起来智能制造塑造工业新范式来源:人民网原创稿工作人员通过智能终端扫描二维码获得设备信息。受访方供图在山东一家汽车零配件企业的生产车间,工作人员通过智能终端扫描发动机缸体上的二维码,能够准备溯……
电竞媒体集团Gamurs收购多个Enthusiast知名游戏根据曾任职Gamurs媒体集团旗下DotEsports网站首席记者的JacobWolf最新报道称,Gamurs集团正在从EnthusiastGaming收购一系列游戏网站。……
商家个人请注意,iPhone14系列已有监管机!购买需谨慎iPhone14系列才发布不到一周,想不到市面上就已经有监管机了!现阶段大部分个人用户都在网上抢iPhone14系列,所以碰到iPhone14监管机的概率比较小。但商家最近在大……