Задержка распределения кажется высокой, почему? - PullRequest
8 голосов
/ 17 ноября 2009

У меня есть (java) приложение, которое работает в среде с низкой задержкой, оно обычно обрабатывает инструкции в ~ 600 микросхем (+/- 100). Естественно, когда мы продвинулись дальше в микросекундное пространство, то, как вы видите, задержка затрат меняется, и прямо сейчас мы заметили, что 2/3 этого времени тратится на выделение двух основных доменных объектов.

Бенчмаркинг изолировал ошибочные разделы кода, чтобы буквально построить объекты из существующих ссылок, то есть, в основном, загрузки ссылок (~ 15 в каждом классе) и нескольких списков new'ed, хотя см. Примечание ниже относительно точно что измеряется здесь

Каждый последовательно берет ~ 100 микрон, что для меня необъяснимо, и я пытаюсь выяснить, почему. Быстрый тест позволяет предположить, что объект аналогичного размера, полный строк, требует около 2-3 микрон, чтобы создать новый, очевидно, этот тип теста сопряжен с трудностями, но подумал, что он может быть полезен в качестве базового уровня.

Здесь есть 2 Qs

  • как можно исследовать такое поведение?
  • Какие есть объяснения медленного распределения?

Обратите внимание, что используется аппаратное обеспечение Solaris 10 x86 на Sun X4600 с 8 * двухъядерными процессорами @ 3,2 ГГц

Вещи, на которые мы смотрели, включают

  • проверка статистики PrintTLAB, показывает v несколько медленных распределений, поэтому здесь не должно быть конфликтов.
  • PrintCompilation предполагает, что один из этих кусков кода не является JIT-дружественным, хотя Solaris, похоже, имеет здесь несколько необычное поведение (то есть, по сравнению с современным linux, у вас нет linux, похожего на vintage и solaris10, на котором можно было бы работать прямо сейчас)
  • LogCompilation ... немного сложнее разобрать, если не сказать больше, так что это постоянная работа, пока ничего очевидного
  • версии JVM ... совместимы для 6u6 и 6u14, еще не пробовали 6u18 или последние 7 еще

Любые и все мысли оценены

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

  • измеряемая мной стоимость - это общая стоимость создания объекта, созданного с помощью Builder (например, одного из этих ), чей приватный конструктор несколько раз вызывает новый ArrayList, а также устанавливает ссылки к существующим объектам. Измеренная стоимость покрывает затраты на настройку компоновщика и преобразование компоновщика в объект домена
  • компиляция (по горячей точке) оказывает заметное влияние, но она все еще относительно медленная (компиляция в этом случае сокращает ее с 100 до 100)
  • Компиляция (по горячей точке) в моем наивном бенчмарке сокращает время выделения с ~ 2micros до ~ 300ns
  • задержка не зависит от алгоритма сбора молодого поколения (ParNew или Parallel scavenge)

Ответы [ 5 ]

3 голосов
/ 07 января 2010

Когда вы повторяете одну и ту же задачу много раз, ваш ЦП имеет тенденцию работать очень эффективно. Это связано с тем, что время простоя вашего кеша и прогрев процессора не учитываются. Также возможно, что вы не учитываете и теплое время JVM.

Если вы попытаетесь сделать то же самое, когда JVM и / или процессор не прогреются. Вы получите совсем другие результаты.

Попробуйте сделать то же самое, скажем 25 раз (меньше, чем ваш порог компиляции) и спать (100) между тестами. Вы должны ожидать увидеть намного более высокие времена, ближе к тому, что вы видите в реальном приложении.

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

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

import java.io.*;
import java.util.Date;

/**
Cold JVM with a Hot CPU took 123 us average
Cold JVM with a Cold CPU took 403 us average
Cold JVM with a Hot CPU took 314 us average
Cold JVM with a Cold CPU took 510 us average
Cold JVM with a Hot CPU took 316 us average
Cold JVM with a Cold CPU took 514 us average
Cold JVM with a Hot CPU took 315 us average
Cold JVM with a Cold CPU took 545 us average
Cold JVM with a Hot CPU took 321 us average
Cold JVM with a Cold CPU took 542 us average
Hot JVM with a Hot CPU took 44 us average
Hot JVM with a Cold CPU took 111 us average
Hot JVM with a Hot CPU took 32 us average
Hot JVM with a Cold CPU took 96 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 80 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 90 us average
Hot JVM with a Hot CPU took 25 us average
Hot JVM with a Cold CPU took 98 us average
 */
public class HotColdBenchmark {
    public static void main(String... args) {
        // load all the classes.
        performTest(null, 25, false);
        for (int i = 0; i < 5; i++) {
            // still pretty cold
            performTest("Cold JVM with a Hot CPU", 25, false);
            // still pretty cold
            performTest("Cold JVM with a Cold CPU", 25, true);
        }

        // warmup the JVM
        performTest(null, 10000, false);
        for (int i = 0; i < 5; i++) {
            // warmed up.
            performTest("Hot JVM with a Hot CPU", 25, false);
            // bit cold
            performTest("Hot JVM with a Cold CPU", 25, true);
        }
    }

    public static long performTest(String report, int n, boolean sleep) {
        long time = 0;
        long ret = 0;
        for (int i = 0; i < n; i++) {
            long start = System.nanoTime();
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(new Date());
                oos.close();
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
                Date d = (Date) ois.readObject();
                ret += d.getTime();
                time += System.nanoTime() - start;
                if (sleep) Thread.sleep(100);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        if (report != null) {
            System.out.printf("%s took %,d us average%n", report, time / n / 1000);
        }
        return ret;
    }
}
3 голосов
/ 18 ноября 2009

Поскольку ваш вопрос был больше о том, как приступить к исследованию проблемы, а не о том, «в чем моя проблема», я остановлюсь на некоторых инструментах, чтобы попробовать.

Очень полезный инструмент для лучшего понимания того, что происходит и когда BTrace . Это похоже на DTrace, однако чистый инструмент Java. На этой ноте я предполагаю, что вы знаете DTrace, если нет, то это также полезно, если не тупо Это даст вам некоторое представление о том, что происходит и когда в JVM и ОС.

О, еще одна вещь, которую нужно уточнить в вашей первоначальной публикации. Какой коллекционер у вас работает? Я предполагаю, что с большой задержкой вы используете коллектор с низкой паузой, такой как CMS. Если да, то пробовал ли ты какой-нибудь тюнинг?

2 голосов
/ 23 декабря 2009

Просто некоторые дикие догадки:

Насколько я понимаю, виртуальные машины Java обрабатывают память недолговечных объектов иначе, чем долгосрочные объекты. Мне кажется разумным, что в тот момент, когда объект переходит от одной ссылки на локальную функцию к наличию ссылок в глобальной куче, будет большим событием. Вместо того, чтобы быть доступным для очистки при выходе из функции, теперь он должен отслеживаться GC.

Или, возможно, переход от одной ссылки к нескольким ссылкам на один объект должен изменить учет ГХ. Пока объект имеет единственную ссылку, его легко очистить. У нескольких ссылок могут быть циклы ссылок, и / или ГХ, возможно, придется искать ссылку во всех других объектах.

2 голосов
/ 18 ноября 2009

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

... мои два цента

2 голосов
/ 18 ноября 2009

Распределение памяти может вызвать побочные эффекты. Возможно ли, что выделение памяти вызывает сжатие кучи? Вы смотрели, чтобы убедиться, что выделение памяти вызывает одновременную работу ГХ?

Вы отдельно рассчитали, сколько времени потребуется для создания новых списков массивов?

...