как убедиться, что не происходит оптимизация jvm и компилятора - PullRequest
17 голосов
/ 09 марта 2011

У меня есть этот код, который тестирует Calendar.getInstance().getTimeInMillis() против System.currentTimeMilli():

long before = getTimeInMilli();
for (int i = 0; i < TIMES_TO_ITERATE; i++)
{
  long before1 = getTimeInMilli();
  doSomeReallyHardWork();
  long after1 = getTimeInMilli();
}
long after = getTimeInMilli();
System.out.println(getClass().getSimpleName() + " total is " + (after - before));

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

Как быть уверенным?

EDIT : я изменил пример кода, чтобы он был более понятным. Здесь я проверяю, сколько времени требуется для вызова getTimeInMilli () в различных реализациях - Календарь против Системы.

Ответы [ 4 ]

29 голосов
/ 09 марта 2011

Я думаю, вам нужно отключить JIT. Добавьте к вашей команде запуска следующую опцию:

-Djava.compiler=NONE
10 голосов
/ 09 марта 2011

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

Однако, если вы хотите убедиться, что JVM не удаляет вызовы, которые в противном случае могли бы считать невозможными операции, одним из вариантов является использование результата - поэтому, если вы вызываете System.currentTimeMillis() несколько раз, вы можете суммируйте все возвращаемые значения и затем отобразите сумму в конце.

Обратите внимание, что вы все еще можете иметь некоторую предвзятость, хотя, например, может быть некоторая оптимизация, если JVM может дешево определить, что с момента последнего вызова System.currentTimeMillis() прошло только крошечное количество времени, поэтому она может использовать кэшированное значение. Я не говорю, что это на самом деле дело здесь, но это та вещь, о которой вам нужно подумать. В конечном итоге, тесты могут реально проверить только те нагрузки, которые вы им даете.

Еще одна вещь, которую следует учитывать: если вы хотите смоделировать реальную ситуацию, в которой код запускается лот , вам следует многократно запускать код, прежде чем использовать какой-либо тайминг - потому что JVM Hotspot оптимизирует постепенно усложняется, и, по-видимому, вам небезразлична сильно оптимизированная версия, а не не хочет измерять время для JITting и «медленных» версий кода.

Как уже упоминал Стивен, вы почти наверняка должны взять время вне цикла ... и не забывать на самом деле использовать результаты ...

3 голосов
/ 09 марта 2011

Извините, но то, что вы пытаетесь сделать, не имеет особого смысла.

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

Время между включением и выключением JIT может сильно отличаться.Вы вряд ли захотите запускать что-либо в производственном режиме с отключенным JIT.

Лучшим подходом было бы сделать это:

long before1 = getTimeInMilli();
for (int i = 0; i < TIMES_TO_ITERATE; i++) {
    doSomeReallyHardWork();
}
long after1 = getTimeInMilli();

... и / или использовать наносекундные часы.


Если вы пытаетесь измерить время, необходимое для вызова двух версий getTimeInMillis(), тогда я не понимаю смысл вашего звонка на doSomeReallyHardWork().Более разумный тест был бы такой:

public long test() {
    long before1 = getTimeInMilli();
    long sum = 0;
    for (int i = 0; i < TIMES_TO_ITERATE; i++) {
        sum += getTimeInMilli();
    }
    long after1 = getTimeInMilli();
    System.out.println("Took " + (after - before) + " milliseconds");
    return sum;
}

... и вызывать его несколько раз, пока напечатанное время не стабилизируется.

В любом случае, моя главная точка все еще стоит, поворачиваясьJIT-компиляции и / или оптимизации означало бы, что вы измеряли что-то, что не полезно знать, а не то, что вы действительно пытаетесь выяснить.(Если, конечно, вы не собираетесь запускать свое приложение в рабочем состоянии с отключенным JIT ... в которое мне трудно поверить ...)

1 голос
/ 09 марта 2011

То, что вы делаете, похоже на бенчмаркинг, вы можете прочитать Надежный бенчмаркинг Java , чтобы получить хорошие знания о том, как все сделать правильно.Короче говоря, вам не нужно его отключать, потому что это не будет происходить на производственном сервере ... вместо этого вам нужно знать, насколько близко возможна оценка в реальном времени / производительность.Перед оптимизацией вам нужно «прогреть» свой код, он выглядит следующим образом:

// warm up
for (int j = 0; j < 1000; j++) {
    for (int i = 0; i < TIMES_TO_ITERATE; i++)
    {
        long before1 = getTimeInMilli();
        doSomeReallyHardWork();
        long after1 = getTimeInMilli();
    }
}

// measure time
long before = getTimeInMilli();
for (int j = 0; j < 1000; j++) {
    for (int i = 0; i < TIMES_TO_ITERATE; i++)
    {
        long before1 = getTimeInMilli();
        doSomeReallyHardWork();
        long after1 = getTimeInMilli();
    }
}
long after = getTimeInMilli();

System.out.prinltn( "What to expect? " + (after - before)/1000 ); // average time

Когда мы измеряем производительность нашего кода, мы используем этот подход, это дает нам более реальное время, необходимое для работы нашего кода.Еще лучше измерить код в отдельных методах:

public void doIt() {
    for (int i = 0; i < TIMES_TO_ITERATE; i++)
    {
        long before1 = getTimeInMilli();
        doSomeReallyHardWork();
        long after1 = getTimeInMilli();
    }
}

// warm up
for (int j = 0; j < 1000; j++) {
    doIt()
}

// measure time
long before = getTimeInMilli();
for (int j = 0; j < 1000; j++) {
    doIt();
}
long after = getTimeInMilli();

System.out.prinltn( "What to expect? " + (after - before)/1000 ); // average time

Второй подход более точен, но он также зависит от виртуальной машины.Например, HotSpot может выполнять «замену в стеке» , это означает, что если некоторая часть метода выполняется очень часто, она будет оптимизирована виртуальной машиной, а старая версия кода будет заменена оптимизированной во время выполнения метода,Конечно, это требует дополнительных действий со стороны ВМ.JRockit этого не делает, оптимизированная версия кода будет использоваться только при повторном выполнении этого метода (поэтому оптимизация «во время выполнения» отсутствует ... Я имею в виду, что в моем первом примере кода будет выполняться весь старый код… кромеdoSomeReallyHardWork внутренние компоненты - они не относятся к этому методу, поэтому оптимизация будет работать хорошо).

ОБНОВЛЕНО: рассматриваемый код был отредактирован, когда я отвечал;)

...