Среда выполнения Android (ART) была значительно улучшена в версии Android 8.0. Ниже представлен список улучшений, которые производители устройств могут ожидать от ART.
Параллельное уплотнение сборщика мусора
Как было объявлено на конференции Google I/O, в Android 8.0 ART представляет новый параллельный уплотняющий сборщик мусора (GC). Этот сборщик сжимает кучу при каждом запуске GC и во время работы приложения, делая лишь одну короткую паузу для обработки корней потоков. Вот его преимущества:
- Сборщик мусора всегда сжимает кучу: в среднем размер кучи на 32% меньше по сравнению с Android 7.0.
- Сжатие позволяет выделять объекты указателей выталкивания локального потока: выделение происходит на 70% быстрее, чем в Android 7.0.
- На 85% меньше времени паузы для теста H2 по сравнению с Android 7.0 GC.
- Длительность пауз больше не зависит от размера кучи; приложения смогут использовать большие кучи, не беспокоясь о задержках.
- Подробности внедрения GC — барьеры чтения:
- Барьеры чтения представляют собой небольшой объем работы, выполняемой для каждого чтения поля объекта.
- Они оптимизированы в компиляторе, но могут замедлить некоторые сценарии использования.
Оптимизация цикла
В версии Android 8.0 ART использует широкий спектр оптимизаций циклов:
- Исключения при проверке границ
- Статика: диапазоны доказываются находящимися в пределах границ во время компиляции
- Динамичность: тесты во время выполнения гарантируют, что циклы остаются в пределах границ (в противном случае — отключаются)
- Исключение индукционных переменных
- Удалить мертвую индукцию
- Заменить индукцию, которая используется только после цикла, на выражения замкнутой формы
- Устранение мертвого кода внутри тела цикла, удаление целых циклов, которые становятся мертвыми
- Снижение прочности
- Циклические преобразования: обращение, перестановка, расщепление, развертывание, унимодулярность и т. д.
- SIMDизация (также называется векторизацией)
Оптимизатор циклов находится в отдельном проходе оптимизации в компиляторе ART. Большинство оптимизаций циклов аналогичны оптимизациям и упрощениям в других местах. Проблемы возникают с некоторыми оптимизациями, которые переписывают CFG более сложным, чем обычно, образом, поскольку большинство утилит CFG (см. nodes.h) сосредоточены на построении CFG, а не на его переписывании.
Анализ иерархии классов
ART в Android 8.0 использует анализ иерархии классов (CHA) — оптимизацию компилятора, которая девиртуализирует виртуальные вызовы в прямые вызовы на основе информации, полученной в результате анализа иерархий классов. Виртуальные вызовы требуют больших затрат, поскольку реализуются на основе поиска в таблице виртуальных функций и требуют нескольких зависимых загрузок. Кроме того, виртуальные вызовы нельзя встроить.
Ниже приведен краткий обзор соответствующих улучшений:
- Динамическое обновление статуса метода с одной реализацией. В конце времени связывания классов, когда vtable заполнена, ART проводит сравнение каждой записи с vtable суперкласса.
- Оптимизация компилятора — компилятор использует информацию об одной реализации метода. Если для метода A.foo установлен флаг одной реализации, компилятор девиртуализирует виртуальный вызов, превращая его в прямой вызов, и затем попытается встроить прямой вызов.
- Аннулирование скомпилированного кода. Также в конце времени компоновки классов, когда обновляется информация об единственной реализации, если метод A.foo, который ранее имел единственную реализацию, но теперь стал недействительным, весь скомпилированный код, зависящий от предположения, что метод A.foo имеет единственную реализацию, должен быть аннулирован.
- Деоптимизация. Для скомпилированного в реальном времени кода, находящегося в стеке, будет инициирована деоптимизация, чтобы принудительно перевести недействительный скомпилированный код в режим интерпретатора для обеспечения корректности. Будет использоваться новый механизм деоптимизации, представляющий собой гибрид синхронной и асинхронной деоптимизации.
Встроенные кэши в файлах .oat
ART теперь использует встроенное кэширование и оптимизирует точки вызова, для которых имеется достаточно данных. Функция встроенного кэширования записывает дополнительную информацию о времени выполнения в профили и использует её для динамической оптимизации предварительной компиляции.
Dexlayout
Dexlayout — это библиотека, представленная в Android 8.0 для анализа dex-файлов и переупорядочивания их в соответствии с профилем. Dexlayout использует информацию профилирования времени выполнения для переупорядочивания разделов dex-файла во время компиляции в режиме ожидания на устройстве. Группируя части dex-файла, к которым часто обращаются одновременно, программы могут оптимизировать доступ к памяти благодаря улучшенной локальности, экономя оперативную память и сокращая время запуска.
Поскольку в настоящее время информация профиля доступна только после запуска приложений, dexlayout интегрируется в компиляцию dex2oat на устройстве во время простоя.
Удаление кэша Dex
До Android 7.0 объект DexCache владел четырьмя большими массивами, пропорциональными количеству определенных элементов в DexFile, а именно:
- строки (одна ссылка на DexFile::StringId),
- типы (одна ссылка на DexFile::TypeId),
- методы (один собственный указатель на DexFile::MethodId),
- поля (один собственный указатель на DexFile::FieldId).
Эти массивы использовались для быстрого извлечения объектов, которые мы ранее разрешали. В Android 8.0 все массивы были удалены, за исключением массива methods.
Производительность переводчика
Производительность интерпретатора значительно возросла в версии Android 7.0 благодаря появлению mterp — интерпретатора с базовым механизмом выборки/декодирования/интерпретации, написанного на языке ассемблера. Mterp создан по образцу быстрого интерпретатора Dalvik и поддерживает платформы arm, arm64, x86, x86_64, mips и mips64. В вычислительном коде mterp Арта примерно сопоставим с быстрым интерпретатором Dalvik. Однако в некоторых ситуациях он может быть значительно — и даже радикально — медленнее:
- Вызов производительности.
- Манипулирование строками и другие активные пользователи методов, признанных встроенными в Dalvik.
- Более высокое использование стековой памяти.
Android 8.0 решает эти проблемы.
Больше встраивания
Начиная с Android 6.0, ART может встраивать любые вызовы в пределах одного dex-файла, но встраивать можно только конечные методы из разных dex-файлов. Это ограничение возникло по двум причинам:
- Встраивание из другого dex-файла требует использования кэша dex этого другого dex-файла, в отличие от встраивания из того же dex-файла, которое может просто повторно использовать кэш dex вызывающего кода. Кэш dex необходим в скомпилированном коде для нескольких инструкций, таких как статические вызовы, загрузка строк или загрузка классов.
- Карты стека кодируют только индекс метода в текущем файле dex.
Чтобы устранить эти ограничения, Android 8.0:
- Удаляет доступ к кэшу Dex из скомпилированного кода (см. также раздел «Удаление кэша Dex»).
- Расширяет кодирование стековой карты.
Улучшения синхронизации
Команда ART настроила пути кода MonitorEnter/MonitorExit и уменьшила нашу зависимость от традиционных барьеров памяти на ARMv8, заменив их более новыми инструкциями (получение/освобождение), где это возможно.
Более быстрые собственные методы
Более быстрые нативные вызовы Java Native Interface (JNI) доступны с помощью аннотаций @FastNative
и @CriticalNative
. Эти встроенные оптимизации среды выполнения ART ускоряют переходы между JNI и заменяют устаревшую нотацию !bang JNI . Аннотации не влияют на ненативную нотацию и доступны только для платформенного кода Java в bootclasspath
(обновления в Play Маркете не предусмотрены).
Аннотация @FastNative
поддерживает нестатические методы. Используйте её, если метод обращается к jobject
как к параметру или возвращаемому значению.
Аннотация @CriticalNative
обеспечивает еще более быстрый способ запуска собственных методов со следующими ограничениями:
- Методы должны быть статическими — никаких объектов для параметров, возвращаемых значений или неявного
this
. - В собственный метод передаются только примитивные типы.
- Собственный метод не использует параметры
JNIEnv
иjclass
в определении функции. - Метод необходимо зарегистрировать в
RegisterNatives
, а не полагаться на динамическое связывание JNI.
@FastNative
может повысить производительность нативных методов до 3 раз, а @CriticalNative
— до 5 раз. Например, переход JNI, измеренный на устройстве Nexus 6P:
Вызов Java Native Interface (JNI) | Время выполнения (в наносекундах) |
---|---|
Регулярный JNI | 115 |
!bang JNI | 60 |
@FastNative | 35 |
@CriticalNative | 25 |