Подтвердить что ты не робот

Регрессия производительности при переходе с jdk1.7.0_25 на jdk1.7.0_40

Я переношу пакетное приложение Spring 3.1.2 от jdk1.7.0_25 до jdk1.7.0_40 как x64, так и Oracle.

Используя Sun OperatingSystemMXBean.getProcessCpuTime() в качестве показателя производительности, результаты показывают снижение производительности на 2,5 раза (т.е. мое приложение, работающее на u25, намного быстрее).

Некоторые общие замечания:

  • Эта проблема появляется для каждой версии JDK 7 >= u40 (а также последней версии JDK 8 jdk1.8.0), тогда как версии < u40 выглядит просто отлично (включая различные версии JDK 6).
  • Обычный старый код Java (например, 1000 * 1000 * 1000 * some_calc) имеет не эту проблему производительности, что означает, что каким-то образом мой код или используемые библиотеки делают что-то странное и неожиданное?
  • Тестирование проводилось с использованием одного и того же экземпляра базы данных (MSSQL 2008 R2), но это не важно.
  • Даже если OperatingSystemMXBean ненадежна, время наступления тестов одинаково.
  • В обоих случаях GC, похоже, запускается в одно и то же время и для тех же длительностей (я использовал +UseSerialGC для тестирования).
  • Профилирование не показывает никаких необычных новых горячих точек, хотя обычно оно показывает увеличение приложения во время выполнения приложения.
  • Тестирование x86-версий этих версий Sun JDK или OpenJDK (я использовал эти) не меняет результат.
  • Весь проверенный код (кроме случаев, когда он запущен на JDK 6) был скомпилирован с помощью jdk1.7.0_40.
  • Тот же сценарий был протестирован на двух разных компьютерах: x64 и x86.

Любые советы или идеи?

Отредактировано для добавления: Структура приложения представляет собой внешний цикл, который запускает моделирование финансового монта-карло: то есть множество дат, вычислений и т.д. Таким образом, в настоящее время он немного сложный и, я согласен, не идеален для поиска вопрос. Я должен попытаться уменьшить его.

4b9b3361

Ответ 1

Похоже, проблема связана с работой, выполненной в 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,50 ГГц

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 и мощности > 2 ints (I только что протестированная Math.pow(x, 3)) производительность была улучшена в jdk1.7.0_40. Что касается странного не-int Math.pow()s, как сделано выше в тесте powx(), по-прежнему сохраняется значительная регрессия производительности ( > 3x) при переходе от jdk1.7.0_25 в jdk1.7.0_40.

Замена Math.pow() и Math.exp() их соответствующими методами в org.apache.commons.math3.util.FastMath полностью решает проблему с увеличением производительности - это правильное решение, насколько мне известно.

Примечание. Это было бы несколько проще, если бы был простой способ (т.е. без требования построения JDK) установить флаг -XX:-InlineIntrinsics.