И снова про инструменты разработки. Часто бывает необходимо сравнить производительность/пропускную способность того или иного участка кода, а писать тестирующий код ой как не хочется. А ведь надо всего-то, запустить нужный метод N раз и померять время выполнения.
Вот сегодня у меня возник вопрос. Сколько процессору надо времени, чтобы проитерироваться по массиву с заданной длинной?
Недолго думая, пишем простой POJO класс описывающий тестовый случай.
package com.blogspot.dotsid.ldt;
public class ArrayIterationTest {
private int size;
private int[] data;
public void setSize(int size) {
this.size = size;
}
public void prepare() {
data = new int[size];
}
public void doTest() {
for ( int i : data );
}
}
компилируем исходник и находясь в classpath'е выполняем:
bazhenov@home ldt -z com.blogspot.dotsid.ArrayIterationTest#doTest -n 100 -p "size=1000000"
RESULTS
--------------------------------------------------
Concurrency level : 1
Samples count (per thread) : 100
Total time : 180ms
Min. time : 1ms
Max. time : 5ms
Throughput : 553 tps
В этом тесте мы создали массив размером 1 миллион позиций и проитерировались по нему 100 раз. Как видно мой процессор по этому массиву пробегает со скоростью несколько миллисекунд на одну полную итерацию.
Довольно простой и эффективный инструмент для выполнения нагрузочных тестов на отдельные модули системы. Основные возможности:
- поддержка многопоточного тестирования;
- поддержка warm up периода (указанное число первых прогонов теста может не участвовать в измерениях. Это бывает необходимо для обеспечения hot code execution path);
- поддержка фикстур (prepare/cleanup);
- sub millisecond accuracy;
- поддержка maven;
А не правильнее ли было бы измерять тики (или что там ещё можно измерить), а не мс?
ОтветитьУдалитьТут надо уточнить. Способ измерения основан как раз на tick'ах, просто потому что системный таймер не предоставляет возможности измерять время с точностью большей чем десятки миллисекунд (зависит от железа), а этого иногда недостаточно. Поэтому, для измерений используются tick'и, но результат для удобства интерпретации представляется в микро/миллисекундах.
ОтветитьУдалитьа как же nanoTime, но больше удивляет конструкция
ОтветитьУдалитьfor ( int i : data );
как правило её jit оптимизирует в nop - и т.о. замер nop-а это точный 0. с другой стороны гонять java приложение без jit'а очень сомнительная задача
Ну на самом деле я nanoTime'ом и пользуюсь. System.nanoTime() использует как раз выше описанный механизм замера времени и предназначен как раз для получения elapsed time (наверное, стоило об этом сразу упомянуть).
ОтветитьУдалитьКонструкция for ( int i : data ); взята как тестовая. Я не уверен оптимизирует ли JIT эту конструкцию в NOP. По идее это может сделать и сам компилятор java, но он этого явно не делает:
public void doTest();
Code:
0: aload_0
1: getfield #3; //Field data:[I
4: astore_1
5: aload_1
6: arraylength
7: istore_2
8: iconst_0
9: istore_3
10: iload_3
11: iload_2
12: if_icmpge 26
15: aload_1
16: iload_3
17: iaload
18: istore 4
20: iinc 3, 1
23: goto 10
26: return
К тому же NOP, насколько мне известно, не может иметь нулевой замер, так как процессор все же "исполняет" эту инструкцию. То есть выполняет fetch и decode.
Видимо мне стоило подобрать менее синтетический пример для демонстрации :)
как показало вскрытие (с) - этот цикл не был выкинут jit'ом (что на мой взгляд странно). а вот цикл вида
ОтветитьУдалитьfor(int i = 0; i < 1000000; i++){int j = 2 * i;}
выкидывает без зазрения совести
Видимо JIT не eliminate'ит блоки кода в которых встречаются reference'ы на non local vaiables. Вне зависимости от того как они используются.
ОтветитьУдалитьЧестно сказать, меня это не удивляет. Никто толком не знает как именно он работает :) JIT для меня это best effort. То есть писать код с оглядкой на то, что JIT соптимизирует конкретные use case'ы это, на мой взгляд, не лучшая идея.
а вот писать performance test'ы лучше всего как раз с этой оглядкой
ОтветитьУдалитьЕсли вы имеете ввиду code elimination, то согласен. Если runtime удалит код, то тестировать нечего, а хороший тест должен что-то тестировать. Другое дело, что я так и не смог найти внятного описания техник противостояния code elimination.
ОтветитьУдалитьВ "Java Concurrency in Practice" Brian'а Goetz'а есть отдельная глава посвященная performance тестированию приложений. И толку от нее мало. Я подозреваю, что большинству программистов все же неизвестно как работает JIT (я уже не говорю про влияние gc на производительность и то что поведение jit меняется от версии к версии).
В большинстве случаев приходится ограничиваться наблюдениями ("что-то тест подозрительно быстро выполняется, надо смотреть disassembly").