Среда выполнения Android (ART) была значительно улучшена в выпуске Android 8.0. В списке ниже приведены улучшения, которые производители устройств могут ожидать в ART.
Параллельное уплотнение сборщика мусора
Как было объявлено на Google I/O, ART представляет новый параллельный уплотняющий сборщик мусора (GC) в Android 8.0. Этот сборщик уплотняет кучу каждый раз, когда запускается 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 использует Class Hierarchy Analysis (CHA), оптимизацию компилятора, которая девиртуализирует виртуальные вызовы в прямые вызовы на основе информации, полученной при анализе иерархий классов. Виртуальные вызовы дороги, поскольку они реализованы вокруг поиска vtable, и они принимают несколько зависимых загрузок. Кроме того, виртуальные вызовы не могут быть встроены.
Ниже приведен краткий обзор соответствующих улучшений:
- Динамическое обновление статуса метода с одной реализацией. В конце времени связывания классов, когда 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 все массивы были удалены, кроме массива методов.
Производительность переводчика
Производительность интерпретатора значительно улучшилась в выпуске Android 7.0 с введением "mterp" - интерпретатора с основным механизмом выборки/декодирования/интерпретации, написанным на языке ассемблера. Mterp смоделирован по образцу быстрого интерпретатора Dalvik и поддерживает arm, arm64, x86, x86_64, mips и mips64. Для вычислительного кода mterp Арта примерно сопоставим с быстрым интерпретатором Dalvik. Однако в некоторых ситуациях он может быть значительно - и даже радикально - медленнее:
- Вызовите производительность.
- Манипулирование строками и другие активные пользователи методов, признанных встроенными в Dalvik.
- Более высокое использование стековой памяти.
Android 8.0 решает эти проблемы.
Больше встраивания
Начиная с Android 6.0, ART может встраивать любой вызов в одни и те же файлы dex, но может встраивать только методы leaf из разных файлов 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 Language платформы в bootclasspath
(без обновлений Play Store).
@FastNative
Annotation поддерживает нестатические методы. Используйте это, если метод обращается к jobject
в качестве параметра или возвращаемого значения.
Аннотация @CriticalNative
обеспечивает еще более быстрый способ запуска собственных методов со следующими ограничениями:
- Методы должны быть статическими — без объектов для параметров, возвращаемых значений или неявного
this
. - В собственный метод передаются только примитивные типы.
- Собственный метод не использует параметры
JNIEnv
иjclass
в определении своей функции. - Метод необходимо зарегистрировать с помощью
RegisterNatives
, а не полагаться на динамическое связывание JNI.
@FastNative
может улучшить производительность собственного метода до 3x, а @CriticalNative
— до 5x. Например, переход JNI, измеренный на устройстве Nexus 6P:
Вызов Java Native Interface (JNI) | Время выполнения (в наносекундах) |
---|---|
Регулярный JNI | 115 |
!bang JNI | 60 |
@FastNative | 35 |
@CriticalNative | 25 |