Улучшения ART в Android 8.0

В Android 8.0 значительно улучшена среда выполнения Android (ART). В списке ниже приведены улучшения, которые производители устройств могут ожидать в ART.

Сборщик мусора с одновременным сжатием

Как было объявлено на конференции Google I/O, в Android 8.0 в ART добавлен новый компактный сборщик мусора (GC), работающий в фоновом режиме. Этот сборщик сжимает кучу каждый раз, когда запускается сборщик мусора, и во время работы приложения, делая лишь одну короткую паузу для обработки корней потоков. Вот его преимущества:

  • Сборщик мусора всегда сжимает кучу: в среднем ее размер на 32% меньше, чем в Android 7.0.
  • Сжатие позволяет выделять объекты указателя локального буфера потока: выделение выполняется на 70% быстрее, чем в Android 7.0.
  • Время паузы в тесте H2 на 85% меньше, чем при сборке мусора в Android 7.0.
  • Время приостановки больше не зависит от размера кучи. Приложения могут использовать большие кучи, не опасаясь сбоев.
  • Особенности реализации сборщика мусора – барьеры чтения:
    • Барьеры чтения – это небольшой объем работы, выполняемый для каждого чтения поля объекта.
    • Они оптимизированы в компиляторе, но могут замедлять некоторые варианты использования.

Оптимизация циклов

В Android 8.0 ART использует множество оптимизаций циклов:

  • Устранение проверок границ
    • Статические: диапазоны проверяются на соответствие границам во время компиляции.
    • Динамический: во время выполнения проверяется, не выходят ли циклы за границы (в противном случае выполняется деоптимизация).
  • Удаление переменных цикла
    • Удаление неработающей индукционной панели
    • Замена индукции, которая используется только после цикла, на выражения в замкнутой форме
  • Удаление мертвого кода внутри тела цикла, удаление целых циклов, которые становятся мертвыми
  • Уменьшение силы
  • Преобразования циклов: обращение, перестановка, разбиение, развертывание, унимодулярное преобразование и т. д.
  • SIMD-оптимизация (также называется векторизацией)

Оптимизатор циклов находится в отдельном проходе оптимизации в компиляторе ART. Большинство оптимизаций циклов похожи на оптимизации и упрощения в других местах. При некоторых видах оптимизации, которые переписывают CFG более сложным образом, чем обычно, возникают проблемы, поскольку большинство утилит CFG (см. nodes.h) предназначены для создания CFG, а не для ее переписывания.

Анализ иерархии классов

В ART для Android 8.0 используется анализ иерархии классов (CHA) – оптимизация компилятора, которая девиртуализирует виртуальные вызовы в прямые на основе информации, полученной в результате анализа иерархий классов. Виртуальные вызовы обходятся дорого, поскольку они реализуются с помощью поиска в таблице виртуальных функций и требуют нескольких зависимых загрузок. Кроме того, виртуальные вызовы нельзя встраивать.

Вот краткий обзор связанных улучшений:

  • Динамическое обновление статуса метода с одной реализацией. В конце времени связывания класса, когда таблица виртуальных методов заполнена, ART выполняет сравнение записей с таблицей виртуальных методов суперкласса.
  • Оптимизация компилятора. Компилятор будет использовать информацию о единственной реализации метода. Если для метода A.foo установлен флаг single-implementation, компилятор девиртуализирует виртуальный вызов в прямой вызов, а затем попытается встроить прямой вызов.
  • Аннулирование скомпилированного кода. Также в конце периода связывания классов, когда обновляется информация об одной реализации, если метод 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. Однако в некоторых ситуациях скорость может быть значительно ниже:

  1. Вызов производительности.
  2. Манипуляции со строками и другие операции, которые активно используют методы, распознаваемые Dalvik как встроенные.
  3. Более высокое использование памяти стека.

В Android 8.0 эти проблемы решены.

Встраивание

Начиная с Android 6.0, ART может встраивать любой вызов в пределах одного DEX-файла, но из разных DEX-файлов можно встраивать только конечные методы. Это ограничение было введено по двум причинам:

  1. Встраивание из другого DEX-файла требует использования кеша DEX этого файла, в отличие от встраивания в том же DEX-файле, при котором можно повторно использовать кеш DEX вызывающего объекта. Кэш DEX необходим в скомпилированном коде для некоторых инструкций, например статических вызовов, загрузки строк или классов.
  2. Карты стека кодируют только индекс метода в текущем файле DEX.

Чтобы устранить эти ограничения, в Android 8.0:

  1. Удаляет доступ к кешу DEX из скомпилированного кода (также см. раздел "Удаление кеша DEX")
  2. Расширяет кодировку карты стека.

Улучшения синхронизации

Команда ART оптимизировала пути выполнения кода MonitorEnter/MonitorExit и уменьшила зависимость от традиционных барьеров памяти на ARMv8, заменив их на более новые инструкции (acquire/release), где это было возможно.

Более быстрые нативные методы

Более быстрые нативные вызовы Java Native Interface (JNI) доступны с помощью аннотаций @FastNative и @CriticalNative. Эти встроенные оптимизации среды выполнения ART ускоряют переходы JNI и заменяют устаревшую нотацию !bang JNI. Аннотации не влияют на методы, не относящиеся к платформе, и доступны только для кода на языке Java платформы в bootclasspath (без обновлений Google Play).

Аннотация @FastNative поддерживает нестатические методы. Используйте этот тип, если метод обращается к объекту jobject как к параметру или возвращаемому значению.

Аннотация @CriticalNative позволяет ещё быстрее выполнять нативные методы, но с некоторыми ограничениями:

  • Методы должны быть статическими – без объектов для параметров, возвращаемых значений или неявного this.
  • В нативный метод передаются только примитивные типы.
  • В нативном методе параметры JNIEnv и jclass не используются в определении функции.
  • Метод должен быть зарегистрирован в RegisterNatives, а не полагаться на динамическую связь JNI.

@FastNative может повысить эффективность встроенного метода в три раза, а @CriticalNative – в пять раз. Например, переход JNI, измеренный на устройстве Nexus 6P:

Вызов Java Native Interface (JNI) Время выполнения (в наносекундах)
Обычный JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25