Создать быстрый / надежный тест с помощью Java?
Я пытаюсь создать тест с помощью Java. В настоящее время у меня есть следующий простой метод:
public static long runTest(int times){
long start = System.nanoTime();
String str = "str";
for(int i=0; i<times; i++){
str = "str"+i;
}
return System.nanoTime()-start;
}
В настоящее время у меня есть этот цикл несколько раз в другом цикле, который происходит несколько раз, и получаю минимальное / максимальное / среднее время, необходимое для выполнения этого метода. Затем я запускаю некоторые действия в другом потоке и снова тестирую. По сути, я просто хочу получить последовательные результаты... Кажется, довольно последовательным, если у меня есть цикл runTest 10 миллионов раз:
Number of times ran: 5
The max time was: 1231419504 (102.85% of the average)
The min time was: 1177508466 (98.35% of the average)
The average time was: 1197291937
The difference between the max and min is: 4.58%
Activated thread activity.
Number of times ran: 5
The max time was: 3872724739 (100.82% of the average)
The min time was: 3804827995 (99.05% of the average)
The average time was: 3841216849
The difference between the max and min is: 1.78%
Running with thread activity took 320.83% as much time as running without.
Но это кажется немного большим и занимает некоторое время... если я попробую меньшее число (100000) в цикле runTest... оно станет очень противоречивым:
Number of times ran: 5
The max time was: 34726168 (143.01% of the average)
The min time was: 20889055 (86.02% of the average)
The average time was: 24283026
The difference between the max and min is: 66.24%
Activated thread activity.
Number of times ran: 5
The max time was: 143950627 (148.83% of the average)
The min time was: 64780554 (66.98% of the average)
The average time was: 96719589
The difference between the max and min is: 122.21%
Running with thread activity took 398.3% as much time as running without.
Есть ли способ, которым я могу сделать такой тест, который будет последовательным и эффективным / быстрым?
Кстати, я не тестирую код между временем начала и окончания. Я тестирую загрузку процессора некоторым способом (посмотрите, как я запускаю некоторую активность потока и повторное тестирование). Так что я думаю, что то, что я ищу, может заменить код, который у меня есть в "runTest", что даст более быстрые и согласованные результаты.
Спасибо
3 ответа
Короче:
(Микро) бенчмаркинг очень сложен, поэтому используйте такой инструмент, как инфраструктура бенчмаркинга http://www.ellipticgroup.com/misc/projectLibrary.zip - и все же скептически относитесь к результатам ("Поместите микро-доверие в микро эталон ", доктор Клифф Клик).
В деталях:
Есть много факторов, которые могут сильно повлиять на результаты:
- Точность и точность System.nanoTime: в худшем случае это так же плохо, как и System.currentTimeMillis.
- прогрев кода и загрузка классов
- смешанный режим: JIT-компиляция JVM (см. ответ Эдвина Бака) только после того, как блок кода вызывается достаточно часто (1500 или 1000 раз)
- динамическая оптимизация: деоптимизация, замена в стеке, удаление мертвого кода (вы должны использовать результат, который вы вычислили в цикле, например, распечатать его)
- восстановление ресурсов: сбор мусора (см. ответ Майкла Боргвардта) и завершение объекта
- кеширование: ввод / вывод и процессор
- Ваша операционная система в целом: заставка, управление питанием, другие процессы (индексатор, проверка на вирусы,...)
Статья Брента Бойера "Надежный бенчмаркинг Java, часть 1: проблемы" ( http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html) представляет собой хорошее описание всех этих проблем, а также того, что / что Вы можете противостоять им (например, использовать параметры JVM или предварительно вызывать ProcessIdleTask).
Вы не сможете устранить все эти факторы, поэтому статистика - хорошая идея. Но:
- вместо того, чтобы вычислять разницу между максимальным и минимальным, вы должны приложить усилия для вычисления стандартного отклонения (результаты {1, 1000 × 2, 3} отличаются от {501 × 1, 501 × 3}).
- Надежность учитывается путем создания доверительных интервалов (например, с помощью начальной загрузки).
Упомянутая выше платформа Benchmark ( http://www.ellipticgroup.com/misc/projectLibrary.zip) использует эти методы. Вы можете прочитать о них в статье Брента Бойера "Надежный бенчмаркинг Java, часть 2: статистика и решения" ( https://www.ibm.com/developerworks/java/library/j-benchmark2/).
Ваш код заканчивается тестированием в основном производительности сборки мусора, потому что добавление в строку в цикле приводит к созданию и немедленному отбрасыванию большого количества все более крупных объектов String.
Это то, что по своей сути приводит к дико изменяющимся измерениям и сильно зависит от многопоточной активности.
Я предлагаю вам сделать что-то еще в вашем цикле с более предсказуемой производительностью, например, математические вычисления.
При 10-миллионном прогоне шансы хороши: компилятор HotSpot обнаружил "интенсивно используемый" фрагмент кода и скомпилировал его в машинный код.
Байт-код JVM интерпретируется, что приводит к тому, что он подвержен большему количеству прерываний от других фоновых процессов, происходящих в JVM (например, сборщик мусора).
Вообще говоря, эти виды тестов изобилуют предположениями, которые не верны. Вы не можете поверить, что микропроцессор действительно доказывает то, что он намеревался доказать, без большого количества доказательств того, что первоначальное измерение (время) на самом деле не измеряет вашу задачу и, возможно, некоторые другие фоновые задачи. Если вы не пытаетесь контролировать фоновые задачи, то измерение будет гораздо менее полезным.