openjdk.ruOpenJDK на русском

JEP 197: Segmented Code Cache

Сегментированный кэш кода

ОтветственныйTobias Hartmann
ТипFeature
ОбластьImplementation
СтатусClosed / Delivered
Выпуск9
Компонентhotspot / compiler
Обсуждениеhotspot dash compiler dash dev at openjdk dot java dot net
ТрудоёмкостьL
ДлительностьM
РецензентыMikael Vidstedt
ОдобренMikael Vidstedt
Создан2014/05/16 08:58
Обновлён2017/04/28 06:26
Задача8043304

Аннотация

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

Цели

  • Разделить неметодный, профилированный и непрофилированный код
  • Сократить время очистки за счёт специализированных итераторов, пропускающих неметодный код
  • Сократить время выполнения некоторых бенчмарков с интенсивной компиляцией
  • Улучшить контроль над объёмом памяти, занимаемой JVM
  • Уменьшить фрагментацию высокооптимизированного кода
  • Улучшить локальность кода, поскольку к коду одного типа, скорее всего, обращаются в близкие моменты времени
    • Улучшить поведение iTLB и iCache
  • Заложить основу для будущих расширений
    • Улучшенное управление разнородным кодом, например Sumatra (код для GPU) и кодом, скомпилированным AOT
    • Возможность детальной блокировки на уровне отдельной кучи кода
    • Будущее разделение кода и метаданных (см. JDK-7072317)

Что не является целью

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

Критерии успеха

  • Разделение кода разных типов
  • Более короткое время очистки
  • Меньшее время выполнения
  • Меньшая фрагментация высокооптимизированного кода
  • Меньшее число промахов iTLB и iCache

Мотивация

Организация и обслуживание скомпилированного кода существенно влияют на производительность. Поступали сообщения о случаях снижения производительности в несколько раз, когда кэш кода предпринимает неверные действия. С появлением многоуровневой компиляции (tiered compilation) роль кэша кода стала ещё важнее, поскольку объём скомпилированного кода увеличивается в 2–4 раза по сравнению с одноуровневой компиляцией. Многоуровневая компиляция также вводит новый тип скомпилированного кода: инструментированный скомпилированный код (профилированный код). Свойства профилированного кода отличаются от свойств непрофилированного; одно из важных различий в том, что у профилированного кода заранее определённое ограниченное время жизни, тогда как непрофилированный код потенциально остаётся в кэше кода навсегда.

Текущий кэш кода оптимизирован для работы с однородным кодом, то есть только с одним типом скомпилированного кода. Кэш кода организован как единая структура данных кучи поверх непрерывного участка памяти. Поэтому профилированный код с заранее определённым ограниченным временем жизни перемешан с непрофилированным кодом, который потенциально остаётся в кэше кода навсегда. Это приводит к различным проблемам производительности и проектирования. Например, механизм очистки методов (method sweeper) при очистке вынужден сканировать весь кэш кода, даже если некоторые записи никогда не удаляются или содержат неметодный код.

Описание

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

  • Внутренний (неметодный) код JVM
  • Профилированный код
  • Непрофилированный код

Соответствующие кучи кода:

  • Куча неметодного кода, содержащая неметодный код, например буферы компилятора и интерпретатор байт-кода. Код этого типа остаётся в кэше кода навсегда.

  • Куча профилированного кода, содержащая слабо оптимизированные профилированные методы с коротким временем жизни.

  • Куча непрофилированного кода, содержащая полностью оптимизированные непрофилированные методы с потенциально долгим временем жизни.

Куча неметодного кода имеет фиксированный размер 3 МБ, рассчитанный на внутренние структуры VM, плюс дополнительное место для буферов компилятора. Это дополнительное место подстраивается в зависимости от числа потоков компиляторов C1/C2. Оставшееся пространство кэша кода распределяется поровну между кучами профилированного и непрофилированного кода.

Для управления размерами куч кода вводятся следующие параметры командной строки:

  • -XX:NonProfiledCodeHeapSize: задаёт размер в байтах кучи кода, содержащей непрофилированные методы.

  • -XX:ProfiledCodeHeapSize: задаёт размер в байтах кучи кода, содержащей профилированные методы.

  • -XX:NonMethodCodeHeapSize: задаёт размер в байтах кучи кода, содержащей неметодный код.

Интерфейс и реализация кэша кода адаптированы для поддержки нескольких куч кода. Поскольку кэш кода — центральный компонент JVM, эти изменения затрагивают многие другие компоненты, в том числе следующие:

  • Механизм очистки кэша кода (code cache sweeper): теперь обходит только кучи кода методов
  • Политика многоуровневой компиляции: задаёт пороги компиляции в зависимости от свободного места в кучах кода
  • Java Flight Recorder (JFR): события, связанные с кэшем кода
  • Косвенные ссылки из:
    • Serviceability Agent: Java-интерфейс к внутренним структурам кэша кода
    • Вспомогательный скрипт DTrace ustack (jhelper.d): разрешение имён скомпилированных Java-методов
    • Библиотека поддержки Pstack (libjvm_db.c): трассировка стека скомпилированных Java-методов

Альтернативы

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

Тестирование

Интенсивное тестирование корректности с использованием JPRT, Nashorn + Octane, SPECjbb2013, SPECjbb2005, SPECjvm2008.

Нам нужно убедиться в отсутствии снижения производительности, особенно во встраиваемых сценариях с небольшими размерами кэша кода.

Тестирование затронутых компонентов, включая Serviceability Agent, DTrace, Pstack, Java Flight Recorder.

Риски и допущения

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

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

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