Почему JVM требует прогрева?

Я понимаю, что в виртуальной машине Java (JVM) потенциально требуется разогрев, поскольку Java загружает классы, используя процесс отложенной загрузки, и поэтому вы хотите убедиться, что объекты инициализируются перед началом основных транзакций. Я разработчик C++ и не сталкивался с подобными требованиями.

Тем не менее, части, которые я не могу понять, следующие:

  1. Какие части кода вы должны разогреть?
  2. Даже если я прогрею некоторые части кода, как долго он будет оставаться теплым (если предположить, что этот термин означает только то, как долго ваши объекты класса остаются в памяти)?
  3. Как это помогает, если у меня есть объекты, которые нужно создавать каждый раз, когда я получаю событие?

В качестве примера рассмотрим приложение, которое, как ожидается, будет получать сообщения через сокет, и транзакции могут быть "Новый заказ", "Изменить заказ" и "Отмена заказа" или "Подтвержденная транзакция".

Обратите внимание, что приложение предназначено для высокочастотной торговли (HFT), поэтому производительность чрезвычайно важна.

7 ответов

Решение

Какие части кода вы должны разогреть?

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

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

Примечание. Warmup может позволить Escape Analysis включить и поместить некоторые объекты в стек. Это означает, что такие объекты не нужно оптимизировать. Лучше запомнить профиль вашего приложения, прежде чем оптимизировать код.

Даже если я прогрею некоторые части кода, как долго он будет оставаться теплым (если предположить, что этот термин означает только то, как долго ваши объекты класса остаются в памяти)?

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

Как это помогает, если у меня есть объекты, которые нужно создавать каждый раз, когда я получаю событие?

Если вам нужна низкая задержка или высокая производительность, вы должны создавать как можно меньше объектов. Я стремлюсь производить менее 300 КБ / с. С этой скоростью распределения вы можете иметь достаточно большое пространство Eden для сбора второстепенных раз в день.

В качестве примера рассмотрим приложение, которое, как ожидается, будет получать сообщения через сокет, и транзакции могут быть "Новый заказ", "Изменить заказ" и "Отмена заказа" или "Подтвержденная транзакция".

Я советую вам как можно чаще использовать объекты, хотя, если они находятся в рамках бюджета на выделение средств, об этом не стоит беспокоиться.

Обратите внимание, что приложение предназначено для высокочастотной торговли (HFT), поэтому производительность чрезвычайно важна.

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

http://chronicle.software/

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

В частности, вас может заинтересовать https://github.com/OpenHFT/Java-Thread-Affinity поскольку эта библиотека может помочь уменьшить дрожание планирования в ваших критических потоках.

А также сказано, что критические разделы кода, требующие прогрева, должны выполняться (с поддельными сообщениями) не менее 12 Кб раз, чтобы он работал оптимизированным образом. Почему и как это работает?

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

Согревание означает, что фрагмент кода выполняется достаточно раз, чтобы JVM перестала интерпретировать и компилировать в нативный (по крайней мере, в первый раз). Как правило, это то, что вы не хотите делать. Причина в том, что JVM собирает статистику о рассматриваемом коде, который он использует во время генерации кода (сродни профилированной оптимизации). Так что, если рассматриваемый кусок кода "подогревается" поддельными данными, которые имеют свойства, отличные от реальных данных, то это может негативно сказаться на производительности.

РЕДАКТИРОВАТЬ: Поскольку JVM не может выполнять статический анализ всей программы (он не может знать, какой код будет загружен приложением), он может вместо этого сделать некоторые предположения о типах из собранной им статистики. Например, при вызове виртуальной функции (на языке C++) в точном месте вызова, и она определяет, что все типы имеют одинаковую реализацию, тогда вызов переводится в прямой вызов (или даже встроенный). Если позднее это предположение окажется ошибочным, старый код должен быть "не скомпилирован" для правильного поведения. AFAIK HotSpot классифицирует колл-сайты как мономорфные (единичная реализация), биоморфные (ровно два.. преобразованные в if (imp1-type) {imp1} else {imp2}) и полностью полиморфные... виртуальная диспетчеризация.

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

Разминка требуется редко. Например, при выполнении тестов производительности важно убедиться, что время прогрева JIT не искажает результаты.

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

Если вы не знаете наверняка, что вам нужна какая-то разминка, не беспокойтесь об этом. Пример приложения, которое вы описали, конечно, не нуждается в нем.

Почему JVM требует разминки?

Современные (J) виртуальные машины собирают статистику во время выполнения о том, какой код используется наиболее часто и как он используется. Один (из сотен, если не тысяч) пример - это оптимизация обращений к виртуальным функциям (на языке C++), которые имеют только реализацию. Эти статистические данные могут по определению собираться только во время выполнения.

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

Какие части кода вы должны разогреть?

Часть, которая имеет решающее значение для производительности вашего приложения. Важная часть заключается в том, чтобы "подогреть" его точно так же, как он используется при обычном использовании, в противном случае будет произведена неправильная оптимизация (и позже будет отменена).

Даже если я прогрею некоторые части кода, как долго он будет оставаться теплым (если предположить, что этот термин означает только то, как долго ваши объекты класса остаются в памяти)?

Это действительно трудно сказать, в основном JIT-компилятор постоянно контролирует выполнение и производительность. Если достигнут некоторый порог, он попытается оптимизировать вещи. Затем он продолжит отслеживать производительность, чтобы убедиться, что оптимизация действительно помогает. Если нет, это может не оптимизировать код. Также могут происходить вещи, которые делают недействительными оптимизации, такие как загрузка новых классов. Я бы посчитал эти вещи непредсказуемыми, по крайней мере, не основываясь на ответе stackru, но есть инструменты, которые скажут вам, что делает JIT: https://github.com/AdoptOpenJDK/jitwatch

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

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

С учетом всего сказанного: я скептически отношусь к мысли, что для разогрева нужно делать что-то особенное. Просто запустите ваше приложение и используйте его, и JIT-компилятор все сделает хорошо. Если у вас возникли проблемы, узнайте, что JIT делает с вашим приложением и как точно настроить это поведение или как написать свое приложение так, чтобы оно приносило наибольшую пользу.

Единственный случай, когда я действительно знаю о необходимости разминки, - это тесты. Потому что, если вы пренебрегаете этим, вы получите поддельные результаты, почти гарантированные.

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

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

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

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

Какие части кода вы должны разогреть?

Там нет ответа на этот вопрос в целом. Это полностью зависит от вашего приложения.

Даже если я прогрею некоторые части кода, как долго он будет оставаться теплым (если предположить, что этот термин означает только то, как долго ваши объекты класса остаются в памяти)?

Объекты остаются в памяти до тех пор, пока ваша программа имеет ссылку на них, при отсутствии какого-либо специального использования со слабой ссылкой или чего-то подобного. Узнать, когда ваша программа "имеет ссылку" на что-то, может быть немного более непонятно, чем вы думаете на первый взгляд, но это основа для управления памятью в Java и стоит усилий.

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

Это полностью зависит от приложения. Там нет ответа в целом.

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

Есть также методы, чтобы узнать, если вы их еще не знаете. Некоторые программы используют "пулы" объектов, которые создаются до того, как они действительно понадобятся, а затем передаются для выполнения обработки при возникновении необходимости. Это позволяет критичной ко времени части программы избежать времени, затрачиваемого на создание экземпляра в течение критического периода. Пулы поддерживают коллекцию объектов (10–100, 1000, 10000?) И создают дополнительные экземпляры, если это необходимо, и т. Д. Но управление пулами - это серьезное программирование, и, конечно, вы занимаете память объектами в пулах.,

Было бы вполне возможно использовать достаточно памяти, чтобы запускать сборку мусора гораздо чаще и замедлять работу системы, которую вы намеревались ускорить. Вот почему вы должны понимать, как это работает, а не просто "получить ответ".

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

- изменить - добавить сборник точно в срок, чтобы изучить и понять.

Я всегда представлял это так:

Вы, как (разработчик C++), можете представить автоматизированный итеративный подход jvm Компиляция / горячая загрузка / замена различных фрагментов (мнимый аналог) gcc -O0,-O1,-O2,-O3 варианты (и иногда возвращая их, если он считает это необходимым)

Я уверен, что это не совсем то, что происходит, но может быть полезной аналогией для разработчика C++.

На стандартном jvm время, необходимое для рассмотрения фрагмента для jit, устанавливается -XX:CompileThreshold который по умолчанию 1500 (Исходники и версии jvm различаются, но я думаю, что это для jvm8)

Далее в книге, которую я имею под рукой, говорится в главе JIT Host Performace (стр. 59), что во время JIT выполняются следующие оптимизации:

  • Встраивание
  • Устранение блокировки
  • Устранение виртуального звонка
  • Устранение записи энергонезависимой памяти
  • Генерация собственного кода

РЕДАКТИРОВАТЬ:

относительно комментариев

Я думаю, что 1500 может быть достаточно, чтобы намекнуть JIT, что он должен скомпилировать код в native и прекратить интерпретацию. Вы бы согласились?

Я не знаю, является ли это просто подсказкой, но поскольку openjdk с открытым исходным кодом, давайте посмотрим на различные ограничения и числа в globals.hpp#l3559@ver-a801bc33b08c (для jdk8u)

(Я не JVM Dev, это может быть совершенно неправильное место, чтобы посмотреть)

Компиляция кода в native не обязательно означает, что он также оптимизирован.

Насколько я понимаю - правда; особенно если вы имеете в виду -Xcomp (принудительная компиляция) - в этом блоге даже говорится, что он не позволяет jvm выполнять профилирование - следовательно, оптимизировать - если вы не запускаете -Xmixed (по умолчанию).

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

Я действительно не знаю деталей, но gobals.hpp Я действительно определил некоторые частотные интервалы.

Другие вопросы по тегам