一段小程序有几种写法,如何评价JVM对其的执行效率

Now7! 2014-11-21

起因是碰巧需要写段小程序,需要检测int x+int y、int x-int y、int x*int y是否会出现overflow或underflow。其实原理蛮简单的,但是需要兼顾一些效率。我调研了一些材料,发现有几种代码的写法:

① int r = x + y; 然后利用位运算符判断:if (((x ^ r) & (y ^ r)) < 0) ……
② long r = (long) x + y; 然后判断:r == (int) r ……
③ 跟①思路一致,但是位运算的写法是: if (((x & y & ~r) | (~x & ~y & r)) < 0) ……

采用①方式的比如Java8的Math. addExact()

http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/Math.java#l779
采用②方式的比如Google Guava的IntMath . checkedAdd () https://github.com/google/guava/blob/master/guava/src/com/google/common/math/IntMath.java
采用③方式的是StackOverflow的一个回答 http://stackoverflow.com/questions/3001836/how-does-java-handle-integer-underflows-and-overflows-and-how-would-you-check-fo

那么问题来了:
[Q1] 如何定性分析这三种写法的效率?简单的想,可以分析生成的bytecode分别有几条指令,每条逻辑执行时间是多少,但是考虑到实际执行时JIT出来的指令及序列会不一样,导致简单分析的结果不对;
[Q2] 如何定量测试这三种写法的效率?就算我们写了测试的code来循环很多次,得到每次平均运行时间,但是实际使用的环境中,如果把这段检测overflow/underflow的代码放到方法里被调用,是不是方法调用开销的本身就比这三段写法的性能差异大多了?再比如在笔记本本地的Windows环境下测试,三者的效率有个相对高低,是否会与服务器Linux环境下实际跑时的相对高低一致呢?

可能问题有些simple & naive, 但还是想看到各位的解答和探讨,谢谢各位~微笑

 

RednaxelaFX 2014-11-21
我只是想说在Java层面讨论这种粒度的细节可能不太合适。

Oracle JDK8 / OpenJDK8里的HotSpot VM里,当被HotSpot Server Compiler(C2)编译过后,Math.addExact()的overflow检查是用硬件自带的指令完成的,开销极低,比各种显式用运算的方式检查都快多了。

例如在x86上,Math.addExact(int, int)int会被编译为类似:
add eax, edx                 # 普通的32位整数加法
jo throw_ArithmeticException # overflow的话跳到抛异常的逻辑
...

因为x86上加法指令都会设置EFLAGS/RFLAGS里的OF(overflow flag),要检测有没有overflow只要在后面用jo(jump if overflow)就好了。其它CPU也是类似,很少CPU是没有这种功能的…

这么一来,在没有overflow的时候无论写什么形式的Java代码都不如直接调用Math.addExact()快;在有overflow的时候则不同,因为Java 8 API的设计还是太煞笔,只提供了抛异常的版本而没提供别的方式返回是否overflow的版本的API,所以如果经常overflow的话用Math.addExact()未必快,要看情况。
(败就败在Java还是没有out参数也不允许返回多值…)
Now7! 2014-11-21
感谢@RednaxelaFX 大大的回复!

R大,你说的硬件指令检测overflow开销极低、overflow之后用低效的Exception来报错所以效率不高这两点解释的很明白,赞

不过下面这点还是很明白,进一步请教下R大:

RednaxelaFX 写道
Oracle JDK8 / OpenJDK8里的HotSpot VM里,当被HotSpot Server Compiler(C2)编译过后,Math.addExact()的overflow检查是用硬件自带的指令完成的


既然Oracle JDK 8/Open JDK 8里的java代码和bytecode都是用的显示运算检查overflow,那基于什么判断HotSpot执行时会编译为类似jo指令而去掉显式的Java位运算呢?比如是基于类名、方法名判断,只针对java.lang.Math做这样的编译,还是基于对bytecode的语义分析知道这段代码其实语义上等同于jo指令所以替换掉呢?
RednaxelaFX 2014-11-21
现在在HotSpot VM里实现的版本是把java.lang.Math.addExact()当作特殊方法,然后整个方法直接用特殊方式实现,绕开原本在里面的字节码。这种做法叫做“intrinsic”。
具体的实现的最初版在这里:http://hg.openjdk.java.net/hsx/hsx25/hotspot/rev/c9ccd7b85f20

我以前在GreenTeaJUG的活动上介绍过HotSpot的intrinsics:
活动帖:http://rednaxelafx.iteye.com/blog/1814180
演示稿:http://www.slideshare.net/RednaxelaFX/green-teajug-hotspotintrinsics02232013
(本来也上传过到新浪微盘,但它被变成了“正在审核”…真纱布)

===================================

我以前实现过的一个原型则是在C2里模式匹配Math.addExact()里的写法,只要用了那种形式的异或来检查溢出,我的代码就会把它匹配上然后把它跟前面的运算关联起来,最后在生成代码的时候生成出jo。这种匹配方式应用范围会更广,但是匹配会比较麻烦而且不太好维护,所以后来没有实际用在产品里。
Now7! 2014-11-21
RednaxelaFX 写道

我以前在GreenTeaJUG的活动上介绍过HotSpot的intrinsics:
活动帖:http://rednaxelafx.iteye.com/blog/1814180
演示稿:http://www.slideshare.net/RednaxelaFX/green-teajug-hotspotintrinsics02232013
(本来也上传过到新浪微盘,但它被变成了“正在审核”…真纱布)

 

RednaxelaFX 写道

我以前实现过的一个原型则是在C2里模式匹配Math.addExact()里的写法……


学习了一遍你的<Intrinsic Methods in HotSpot VM>,收获很多,大赞 
并且在演示稿里找到了Math.addExact(),竟然是“撞到枪口”了 吐舌头

 

 

感谢R大!

ps. Azul付你钱让你在这回复问题的嘛?

RednaxelaFX 2014-11-21
抱歉一个intrinsic把您最初的几个问题全部绕开了。那些也是有趣的问题,值得讨论讨论。

Now7! 写道
ps. Azul付你钱让你在这回复问题的嘛?

哈哈怎么可能。纯兴趣回复。所以也没办法做到所有帖都回复(抱歉

要付我工资在论坛回复问题那估计也得是在Zulu的论坛上了。

况且这个HLLVM群组主要讨论的HotSpot VM都是Oracle版的而不是Azul版的,要给我工资也应该是老东家给(逃

=========================

话说 -XX:+UseMathExactIntrinsics 应该是在JDK8u25才默认开启的,在那之前的JDK8要用intrinsic版的Math.xxxExact的话得要 -XX:+UnlockExperimentalVMOptions -XX:+UseMathExactIntrinsics 才行(当然是因为当时还有bug…)
tiancongyun 2014-11-21
想测试一下loop unroll对性能的影响,有什么办法可以关掉自动unrolling吗?
RednaxelaFX 2014-11-22
tiancongyun 写道
想测试一下loop unroll对性能的影响,有什么办法可以关掉自动unrolling吗?

要关掉C2的loop unrolling试试 -XX:LoopUnrollLimit=0
Global site tag (gtag.js) - Google Analytics