java 怎么循环时间段_java循环长度的相同、循环体代码相同的两次for循环的执行时间相差了100倍?...

哈,发现9个月前就写了草稿。没发出来肯定是因为当时我正要继续写的时候Chrome又崩溃了。

(不怪Chrome,macOS上自带的中文输入法真是见啥杀啥)

简单说,原因是OSR编译时C2没能做充分的常量传播。仅此而已。什么cache miss啊啥的说法都完全不靠谱。

给题主的简要建议是:在HotSpot VM上跑microbenchmark切记不要在main()里跑循环计时就完事。这是典型错误。重要的事情重复三遍:请用JMH,请用JMH,请用JMH。除非非常了解HotSpot的实现细节,在main里这样跑循环计时得到的结果其实对一般程序员来说根本没有任何意义,因为无法解释。

就算计时,用System.nanoTime()也比new Date().getTime()好太多

要给常量赋个别名的话,为了保证靠谱请习惯性加上final。例如说这里题主要是写 final int range = 8000; 就会发现第三层循环的条件改用range也一样快。

跑microbenchmark多留意一下 -XX:+PrintCompilation 的输出。

就酱。

题主说在内层循环里用字面量8000比用同样值的局部变量range要快很多。这是因为题主在main里写循环,触发的是OSR编译——只JIT编译了方法的一部分而没有编译整个方法。题主例子里的局部变量range只被赋值了一次,但在JIT编译器看来这个赋值并不在本次OSR编译覆盖的范围内,所以JIT编译器无法知道其实这个变量实质上是个常量,只知道“喔有这么个变量,它的值会在进入OSR编译的代码时从解释器传过来”,这样编译出来的代码质量就会差很多。

我以前在Azul工作的时候遇到过有客户也问了非常类似的问题,同样是使用了错误的方法写microbenchmark同样是做了错误的解读。当时的状况是同样触发了OSR编译,在循环外定义的“实质常量”没有被常量传播到OSR编译的循环内。而循环内测的是除法的性能,那个常量值比较特别可以换成若干位移和加减来做,如果有常量传播就可以对除法做特化,没有的话则是只能做普通除法就很慢。

当时为了让客户高兴其实我有写一个patch来让HotSpot C1和C2都能在OSR编译的情况下从循环外传播更多常量进来。代码有点复杂而且解决的问题常常只是“又有人乱写microbenchmark”了,所以就没有合并到Azul Zing JVM里,也没有提交给OpenJDK。知道问题所在之后实现思路其实很简单的。

最开头的时候提到了加final修饰局部变量的事。这是Java语言规范层面的规定:一个被final修饰的局部变量,如果其初始化表达式是常量表达式,那么这个局部变量也是常量表达式,它的常量值会被Java语言编译器(例如javac或者ECJ,而不是JVM里的JIT编译器)传播并内嵌到后面的使用点上。所以如果range声明的地方加了final修饰,后面用range的地方就跟直接手写8000效果是一模一样的,从JVM的角度看输入的字节码会完全一样,性能特性当然也完全一样。

至于题主的后一个例子问循环就差1为何性能差那么多,这一部分也是跟JIT编译的触发机制有关的;另外这是在最内层循环差了1,外面还有很大的放大倍数。如果题主只想测Integer cache性能的话请用JMH测,免得要关心那么多JVM的JIT编译系统的实现细节。

如果题主坚持要用原本的代码继续做实验,试试看在差1的那个例子加上 -XX:-TieredCompilation 看性能差多少。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值