从jdk1.7.0_25迁移到jdk1.7.0_40时的性能回归

时间:2014-11-20 10:56:45

标签: java performance java-7

我正在将一个Spring 3.1.2批处理应用程序从jdk1.7.0_25迁移到jdk1.7.0_40 x64和Oracle。

使用Sun的OperatingSystemMXBean.getProcessCpuTime()作为性能指标,结果显示性能下降2.5倍(即,我在u25上运行的应用程序要快得多)。

  • 据我所知,这不是由于java.util.HashMapjava.util.ArrayList changes,因为当使用u25的HashMap和ArrayList类引导u40时结果是相同的,并且这些更改是这种差异太小了。
  • 这也与HashMap并发性regression无关,因为应用程序是单线程的,并且回归在u40中已经修复
  • Hotswap优化似乎也不是问题,因为使用-Xbatch-Xcomp运行会产生相同的结果(假设这些JDK之间的服务器编译是相同的)。
  • 关于java.lang.invoke.MethodHandles的效果regression,但似乎无关紧要。除非Spring 3.1.2使用它们 - 我找不到它的证据。
  • javac编译似乎也没有改变。

一些一般性说明:

  • 每个JDK 7版本> = u40(以及最新的JDK 8 jdk1.8.0)都会出现此问题,而版本< u40似乎很好(包括各种版本的JDK 6)。
  • 普通的旧java代码(例如,运行1000 * 1000 * 1000 * some_calc)会出现此性能问题,这意味着我的代码或使用过的库在做某些奇怪和意外的事情?< / LI>
  • 使用相同的数据库实例(MSSQL 2008 R2)进行测试,而不是它应该重要。
  • 即使OperatingSystemMXBean不可靠,测试的挂起时间也不同。
  • 在这两种情况下,GC似乎都是在相同的时间和相同的持续时间内启动的(我一直在使用+UseSerialGC进行测试)。
  • 分析显示没有异常的新热点,但它通常会显示应用程序的执行时间大幅增加。
  • 测试这些Sun JDK或OpenJDK版本的x86版本(我使用过these)不会改变结果。
  • 所有测试的代码(在JDK 6上运行时除外)都是使用jdk1.7.0_40编译的。
  • 在两台不同的计算机上测试了相同的方案:x64和x86。

任何提示或想法?

编辑添加:应用程序的结构是一个外部循环,运行金融蒙特卡罗模拟:即大量的日期,计算等。因此,它目前有点复杂,我同意,不适合找到问题。我将不得不尝试缩小它。

1 个答案:

答案 0 :(得分:5)

问题似乎是由于在JDK-7133857中完成的工作,其中java.lang.Math.pow()java.lang.Math.exp()是使用x87进行内部化和计算的。

这些方法在配置文件中广泛使用(!),因此效果显着。

JDK-8029302描述并修复了2输入功率的问题;并且使用jdk1.8.0_25(其中问题已修复)对应用程序的测试显示了改进的性能,但在内化完成之前没有回到jdk1.7.0_25的更高级别。

以下是我的JMH基准测试以及Math.pow()在三个相关JDK版本上的结果:

package org.sample;

import org.openjdk.jmh.annotations.*;
import java.lang.*;

public class MyBenchmark {

    @State(Scope.Benchmark)
    public static class ThreadState {
        volatile double x = 0;
        volatile double y = 0;
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public double powx(ThreadState state) {
        state.x++;
        state.y += 0.5;
        return Math.pow(state.x, state.y);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public double pow3(ThreadState state) {
        state.x++;
        return Math.pow(state.x, 3);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public double pow2(ThreadState state) {
        state.x++;
        return Math.pow(state.x, 2);
    }
}

结果:

Intel(R)Core(TM)i5-2520M CPU @ 2.50GHz

jdk1.7.0_25 - 在内在化之前

# VM invoker: x:\@sdks\jdks\jdk1.7.0_25\jre\bin\java.exe
...
Result: 4877658.355 (99.9%) 330460.323 ops/s [Average]
  Statistics: (min, avg, max) = (1216417.493, 4877658.355, 6421780.276), stdev = 1399189.700
  Confidence interval (99.9%): [4547198.032, 5208118.678]


# Run complete. Total time: 00:24:48

Benchmark                Mode  Samples         Score  Score error  Units
o.s.MyBenchmark.pow2    thrpt      200  40160618.138  1561135.596  ops/s
o.s.MyBenchmark.pow3    thrpt      200   3678800.153    88678.269  ops/s
o.s.MyBenchmark.powx    thrpt      200   4877658.355   330460.323  ops/s

jdk1.7.0_40 - 内在化

# VM invoker: x:\@sdks\jdks\jdk1.7.0_40\jre\bin\java.exe
...
Result: 1860849.245 (99.9%) 94303.387 ops/s [Average]
  Statistics: (min, avg, max) = (418909.582, 1860849.245, 2379936.035), stdev = 399286.444
  Confidence interval (99.9%): [1766545.859, 1955152.632]


# Run complete. Total time: 00:24:48

Benchmark                Mode  Samples        Score  Score error  Units
o.s.MyBenchmark.pow2    thrpt      200  9619333.987   230749.333  ops/s
o.s.MyBenchmark.pow3    thrpt      200  9240043.369   238456.949  ops/s
o.s.MyBenchmark.powx    thrpt      200  1860849.245    94303.387  ops/s

jdk1.8.0_25 - 固定内在化

# VM invoker: x:\@sdks\jdks\jdk1.8.0_25\jre\bin\java.exe
...
Result: 1898015.057 (99.9%) 92555.236 ops/s [Average]
  Statistics: (min, avg, max) = (649562.297, 1898015.057, 2359474.902), stdev = 391884.665
  Confidence interval (99.9%): [1805459.821, 1990570.293]


# Run complete. Total time: 00:24:37

Benchmark                Mode  Samples         Score  Score error  Units
o.s.MyBenchmark.pow2    thrpt      200  81840274.815  1979190.065  ops/s
o.s.MyBenchmark.pow3    thrpt      200   9441518.686   206612.404  ops/s
o.s.MyBenchmark.powx    thrpt      200   1898015.057    92555.236  ops/s

如果我正确读到这一点,2问题的强大功能肯定在JDK-8029302和&gt; 2 ints(我刚刚测试Math.pow(x, 3))的功效得到了修复{{1} }。对于jdk1.7.0_40基准测试中上面所做的奇怪的非int Math.pow()s,从powx()移动到{{1}时,似乎仍然存在相当大的性能回归(> 3x) }}

jdk1.7.0_25中使用各自的方法替换jdk1.7.0_40Math.pow()完全解决了性能提升的问题 - 就我而言,这是正确的解决方案。

注意:如果有一种简单的方法(即不需要构建JDK)来设置Math.exp()标志,这会更简单。