Подтвердить что ты не робот

Java-бенчмаркинг - почему второй цикл быстрее?

Мне это интересно.

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

public static void main(String[] args) {

        long ts;
        String c = "sgfrt34tdfg34";

        ts = System.currentTimeMillis();
        for (int k = 0; k < 10000000; k++) {
            c.getBytes();
        }
        System.out.println("t1->" + (System.currentTimeMillis() - ts));

        ts = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            Bytes.toBytes(c);
        }
        System.out.println("t2->" + (System.currentTimeMillis() - ts));

    }

"Второй" цикл работает быстрее, поэтому я думал, что класс Bytes из hadoop был быстрее, чем функция класса String. Затем я изменил порядок циклов, а затем c.getBytes() стал быстрее. Я выполнял много раз, и мой вывод состоял в том, что я не знаю почему, но что-то происходит в моей виртуальной машине после первого запуска кода, чтобы результаты стали быстрее для второго цикла.

4b9b3361

Ответ 1

Это классическая проблема бенчмаркинга Java. Hotspot/JIT/etc будет компилировать ваш код, когда вы его используете, поэтому он становится быстрее во время прогона.

Сначала запустите цикл по крайней мере 3000 раз (10000 на сервере или на 64 бит) - затем выполните ваши измерения.

Ответ 2

Вы знаете, что что-то не так, потому что Bytes.toBytes вызывает c.getBytes внутренне:

public static byte[] toBytes(String s) {
    try {
        return s.getBytes(HConstants.UTF8_ENCODING);
    } catch (UnsupportedEncodingException e) {
        LOG.error("UTF-8 not supported?", e);
        return null;
    }
}

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

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

Ответ 3

"Второй" цикл выполняется быстрее, поэтому

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

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

Лучшее решение состоит в том, чтобы поместить каждый цикл в отдельный метод, чтобы один цикл не оптимизировал другой. И выполняйте это несколько раз, игнорируя первый запуск.

например.

for(int i = 0; i < 3; i++) {
    long time1 = doTest1();  // timed using System.nanoTime();
    long time2 = doTest2();
    System.out.printf("Test1 took %,d on average, Test2 took %,d on average%n",
        time1/RUNS, time2/RUNS);
}

Ответ 4

Скорее всего, код все еще компилировался или еще не скомпилирован в момент запуска первого цикла.

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

Читайте: Динамическая компиляция и измерение производительности.

Ответ 5

Просто может быть так, что вы выделяете столько объектов для объектов с вашими вызовами getBytes(), что запускает сборщик мусора JVM и очищает неиспользуемые ссылки (выводя мусор).

Ответ 6

Несколько наблюдений

  • Как указано @dasblinkenlight выше, Hadoop Bytes.toBytes(c); внутренне вызывает String.getBytes("UTF-8")

  • Вариантный метод String.getBytes(), который принимает набор символов в качестве входного , быстрее, который не принимает никакого набора символов. Поэтому для данной строки getBytes("UTF-8") будет быстрее, чем getBytes(). Я тестировал это на своей машине (Windows8, JDK 7). Запустите две петли с getBytes("UTF-8") и другими с getBytes() последовательно в равных итерациях.

        long ts;
        String c = "sgfrt34tdfg34";
    
        ts = System.currentTimeMillis();
        for (int k = 0; k < 10000000; k++) {
            c.getBytes("UTF-8");
        }
        System.out.println("t1->" + (System.currentTimeMillis() - ts));
    
        ts = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) { 
            c.getBytes();
        }
        System.out.println("t2->" + (System.currentTimeMillis() - ts));
    

это дает:

t1->1970
t2->2541

и результаты будут одинаковыми, даже если вы измените порядок выполнения цикла. Чтобы уклониться от любых оптимизаций JIT, я бы предложил запустить тесты в отдельных методах, чтобы подтвердить это (как было предложено @Peter Lawrey выше)

  • Итак, Bytes.toBytes(c) всегда будет быстрее, чем String.getBytes()