Java: Что вызывает увеличение производительности при повторном вызове функции?

Я проводил некоторое тестирование производительности в отношении размещения объектов, когда наткнулся на странный результат. У меня есть следующий код Java:

public static long TestMethod(){
    int len = 10000000;
    Object[] obs = new Object[len];
    long t = System.nanoTime();
    for (int i = 0; i < len; i++) {
        obs[i] = new Object();
    }
    return System.nanoTime() - t;
}

public static void main(String... args) throws InterruptedException {
    for(int i = 0; i < 10; i++){
        System.gc();
        System.gc();//Wait for the gc to be finished
        Thread.sleep(1000);
        System.out.println(TestMethod());
    }
}

Ожидание: первый вызов будет медленнее второго из-за запроса большего объема памяти от операционной системы и усовершенствований точки доступа. Но второй и третий будут почти одинаковыми.

Наблюдаемый результат:

11284734000
799837000
682304000
304736000
380770000
392786000
374279000
381611000
379174000
407256000

Все еще значительное ускорение между третьей и четвертой итерацией. Чем вызвано это ускорение? Как я могу быть уверен, что мои измерения точны при тестировании других функций, нужно ли вызывать функцию более четырех раз перед измерением?


person user23127    schedule 30.12.2012    source источник
comment
Задержка запуска компилятора Just-in-time   -  person President James K. Polk    schedule 31.12.2012
comment
Интересно прочитать: stackoverflow.com/questions/504103/. В частности, запуск с параметрами -XX:+PrintCompilation -verbose:gc VM поможет вам понять, что происходит.   -  person assylias    schedule 31.12.2012
comment
@assylias: Интересно. Не знал этих параметров. Кажется, что функция перекомпилируется во втором вызове и выполняется 2 сборки мусора в первых трех вызовах, а последние вызовы не выполняют сборку мусора в вызовах функций.   -  person user23127    schedule 31.12.2012


Ответы (2)


Чем вызвано это ускорение?

Скорее всего, это JIT-компиляция, но это также может быть связано с загрузкой кода и/или эффектами прогрева кучи.

Как я могу быть уверен, что мои измерения точны при тестировании других функций, нужно ли вызывать функцию более четырех раз перед измерением?

Вам нужно сделать что-то подобное. Нет другого способа исключить эффекты прогрева JVM из ваших измерений и при этом получить репрезентативные результаты. Написание корректных «микротестов» для Java сложно, и вам нужно изучить все проблемы, прежде чем пытаться. Начните с этого: Как мне написать правильный микротест в Java?


Я бы еще отметил пару моментов:

  1. Ваши попытки исключить стоимость сборки мусора из ваших измерений (я предполагаю, что вы, похоже, именно это и делаете) похоже потерпели неудачу. Похоже, вы получаете второстепенные коллекции во время выполнения testMethod. Это объясняет примерно 7% вариабельность ваших результатов в «устойчивом состоянии».

  2. Разделение затрат на выделение объекта и затрат на его освобождение, скорее всего, даст вам вводящие в заблуждение результаты. «Общая» стоимость выделения объекта включает стоимость обнуления памяти при его повторном использовании... и это делается сборщиком мусора.

  3. На самом деле, наиболее полезным показателем является амортизированная стоимость объекта в цикле выделения/сбора. И это (на удивление) зависит от количества не-мусора при запуске GC... что ваш тест не принимает во внимание.

person Stephen C    schedule 30.12.2012

Как вы заявили, существует несколько факторов, которые могут ускорить выполнение функции:

  • чаще всего JIT может происходить в разное время или в разные моменты вашего приложения (это почему профилирование без предоставления JVM достаточного времени для прогрева может привести к вводящим в заблуждение результатам)
  • запрос памяти к ОС
  • повторное использование памяти в куче для объектов, которые вы выделяете

Вы не можете быть уверены в том, когда происходит каждый шаг, и вы не можете знать, когда фрагменты кода компилируются в машинный код, но если предположить, что это происходит непосредственно перед первым вызовом, то это может произойти именно тогда, когда время падает между третьим и четвертая итерация или что-то в этом роде. К сожалению, эти детали скрыты внутри реализации JVM.

person Jack    schedule 30.12.2012