Создать быстрый/надежный тест с 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;     
}

В настоящее время я использую этот цикл несколько раз в другом цикле, который происходит несколько раз и получает время min/max/avg, необходимое для запуска этого метода. Затем я начинаю работу над другим потоком и снова тестирую. В принципе, я просто хочу получить согласованные результаты... Кажется довольно последовательным, если у меня цикл 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", который даст более быстрые и последовательные результаты.

Спасибо

Ответы

Ответ 1

Короче:

(Micro-) бенчмаркинг очень сложный, поэтому используйте инструмент, подобный платформе Benchmarking http://www.ellipticgroup.com/misc/projectLibrary.zip - и по-прежнему скептически относитесь к результатам ( "Положите микро-доверие в микро-бенчмарк", Dr. Cliff Click).

Подробнее:

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

  • Точность и точность System.nanoTime: это в худшем случае так же плохо, как и System.currentTimeMillis.
  • разгрузка кода и загрузка классов
  • смешанный режим: JVMs JIT-компиляция (см. ответ Эдвина Бака) только после того, как кодовый блок называется достаточно часто (1500 или 1000 раз)
  • динамическая оптимизация: деоптимизация, замена на стеке, устранение мертвого кода (вы должны использовать результат, который вы вычислили в своем цикле, например, распечатать его)
  • рекультивация ресурсов: коллекция одежды (см. ответ Майкла Боргвардта) и завершение объекта
  • кэширование: ввод-вывод и процессор
  • ваша операционная система в целом: экранная заставка, управление питанием, другие процессы (индексатор, проверка на вирусы,...)

Статья Брент Бойер "Прочный Java-бенчмаркинг, часть 1: проблемы" (http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html) является хорошим описанием всех этих проблем и/что вы можете сделать против них (например, используйте опцию JVM или предварительно вызовите ProcessIdleTask).

Вы не сможете устранить все эти факторы, поэтому делать статистику - хорошая идея. Но:

  • вместо вычисления разницы между max и min, вы должны приложить усилия для вычисления стандартного отклонения (результаты {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/).

Ответ 2

Ваш код заканчивается тем, что в основном тестирует сборку мусора, поскольку добавление к String в цикле приводит к созданию и немедленному отбрасыванию большого количества все более крупных объектов String.

Это то, что по своей сути приводит к дико меняющимся измерениям и сильно зависит от многопоточной активности.

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

Ответ 3

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

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

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