В версии Android 8.0 значительно улучшена среда выполнения Android (ART). Ниже приведён краткий список улучшений, которые производители устройств могут ожидать в ART.
Одновременный сборщик мусора для уплотнения
Как было объявлено на Google I/O, в Android 8.0 реализован новый сборщик мусора (GC) с параллельным сжатием. Этот сборщик сжимает кучу каждый раз при запуске GC и во время работы приложения, с одной короткой паузой для обработки корневых потоков. Вот его преимущества:
- Сборщик мусора всегда сжимает кучу: в среднем размер кучи уменьшается на 32% по сравнению с Android 7.0.
- Функция сжатия позволяет выделять локальные объекты указателей потока: выделение памяти происходит на 70% быстрее, чем в Android 7.0.
- Обеспечивает сокращение времени паузы в тесте H2 на 85% по сравнению с Android 7.0 GC.
- Время паузы больше не зависит от размера кучи; приложения должны иметь возможность использовать большие объемы кучи, не опасаясь задержек.
- Детали реализации сборщика мусора — барьеры чтения:
- Барьеры чтения — это небольшой объем работы, выполняемый при чтении каждого поля объекта.
- Эти функции оптимизированы компилятором, но могут замедлить работу в некоторых сценариях использования.
Оптимизация циклов
В Android 8.0 архитектура ART использует широкий спектр оптимизаций циклов:
- Проверка границ, исключение
- Статический режим: диапазоны значений доказано находятся в пределах допустимых значений на этапе компиляции.
- Динамический режим: тесты во время выполнения гарантируют, что циклы остаются в допустимых пределах (в противном случае их следует исключить).
- Исключение переменных индукции
- Удалите мертвую индукционную трубу
- Замените индукцию, используемую только после цикла, выражениями в замкнутой форме.
- Удаление мертвого кода внутри тела цикла, удаление целых циклов, которые становятся мертвыми.
- снижение силы
- Преобразования циклов: обращение, перестановка, разделение, развертывание, унимодулярность и т. д.
- SIMD-инсталляция (также называемая векторизацией)
Оптимизатор циклов находится в отдельном проходе оптимизации компилятора ART. Большинство оптимизаций циклов аналогичны оптимизациям и упрощениям в других местах. Сложности возникают с некоторыми оптимизациями, которые переписывают граф контекста более сложным, чем обычно, способом, поскольку большинство утилит для работы с графами контекста (см. nodes.h) сосредоточены на построении графа контекста, а не на его переписывании.
Анализ иерархии классов
В Android 8.0 ART использует анализ иерархии классов (CHA), оптимизацию компилятора, которая преобразует виртуальные вызовы в прямые вызовы на основе информации, полученной в результате анализа иерархий классов. Виртуальные вызовы являются ресурсоемкими, поскольку они реализуются на основе поиска в таблице виртуальных функций и требуют загрузки нескольких зависимых модулей. Кроме того, виртуальные вызовы нельзя встроить.
Ниже приведено краткое описание соответствующих улучшений:
- Динамическое обновление статуса метода с одной реализацией — по завершении связывания класса, когда таблица виртуальных функций заполнена, ART выполняет пошаговое сравнение каждой записи с таблицей виртуальных функций суперкласса.
- Оптимизация компилятором — компилятор будет использовать информацию о единственной реализации метода. Если для метода 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 от Art примерно сопоставим с быстрым интерпретатором 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 (JNI) доступны с помощью аннотаций @FastNative и @CriticalNative . Эти встроенные оптимизации среды выполнения ART ускоряют переходы JNI и заменяют устаревшую нотацию !bang JNI . Аннотации не влияют на методы, не являющиеся нативными, и доступны только для кода Java, находящегося в bootclasspath (обновления из Play Store не допускаются).
Аннотация @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 |