Melhorias no Android 8.0 ART

O tempo de execução do Android (ART) foi melhorado significativamente na versão Android 8.0. A lista abaixo resume as melhorias que os fabricantes de dispositivos podem esperar do ART.

Coletor de lixo de compactação simultânea

Conforme anunciado no Google I/O, o ART apresenta um novo coletor de lixo (GC) de compactação simultânea no Android 8.0. Esse coletor compacta o heap sempre que o GC é executado e enquanto o aplicativo está em execução, com apenas uma pequena pausa para processar raízes de thread. Aqui estão seus benefícios:

  • O GC sempre compacta o heap: tamanhos de heap 32% menores, em média, em comparação com o Android 7.0.
  • A compactação permite a alocação de objeto de ponteiro de colisão local de thread: as alocações são 70% mais rápidas do que no Android 7.0.
  • Oferece tempos de pausa 85% menores para o benchmark H2 em comparação com o Android 7.0 GC.
  • Os tempos de pausa não são mais dimensionados com o tamanho do heap; os aplicativos devem ser capazes de usar grandes pilhas sem se preocupar com erros.
  • Detalhe da implementação do GC - Barreiras de leitura:
    • As barreiras de leitura são uma pequena quantidade de trabalho realizado para cada campo de objeto lido.
    • Eles são otimizados no compilador, mas podem retardar alguns casos de uso.

Otimizações de loop

Uma grande variedade de otimizações de loop são empregadas pelo ART na versão Android 8.0:

  • Eliminações de verificação de limites
    • Estático: é comprovado que os intervalos estão dentro dos limites em tempo de compilação
    • Dinâmico: testes em tempo de execução garantem que os loops permaneçam dentro dos limites (caso contrário, desative)
  • Eliminações de variáveis ​​de indução
    • Remova a indução morta
    • Substitua a indução que é usada somente após o loop por expressões de forma fechada
  • Eliminação de código morto dentro do corpo do loop, remoção de loops inteiros que se tornam mortos
  • Redução de força
  • Transformações de loop: reversão, troca, divisão, desenrolamento, unimodular, etc.
  • SIMDização (também chamada de vetorização)

O otimizador de loop reside em sua própria passagem de otimização no compilador ART. A maioria das otimizações de loop são semelhantes às otimizações e simplificações em outros lugares. Os desafios surgem com algumas otimizações que reescrevem o CFG de uma forma mais elaborada do que o habitual, porque a maioria dos utilitários de CFG (ver nós.h) concentram-se na construção de um CFG, e não na reescrita de um.

Análise de hierarquia de classes

O ART no Android 8.0 usa Class Hierarchy Analysis (CHA), uma otimização do compilador que desvirtualiza chamadas virtuais em chamadas diretas com base nas informações geradas pela análise de hierarquias de classes. As chamadas virtuais são caras, pois são implementadas em torno de uma pesquisa de vtable e exigem algumas cargas dependentes. Além disso, as chamadas virtuais não podem ser incorporadas.

Aqui está um resumo das melhorias relacionadas:

  • Atualização dinâmica do status do método de implementação única - No final do tempo de vinculação da classe, quando a vtable foi preenchida, o ART conduz uma comparação entrada por entrada com a vtable da superclasse.
  • Otimização do compilador - O compilador aproveitará as vantagens das informações de implementação única de um método. Se um método A.foo tiver um sinalizador de implementação única definido, o compilador desvirtualizará a chamada virtual em uma chamada direta e tentará embutir a chamada direta como resultado.
  • Invalidação de código compilado - Também no final do tempo de vinculação de classe, quando as informações de implementação única são atualizadas, se o método A.foo que anteriormente tinha implementação única, mas esse status agora está invalidado, todo o código compilado que depende da suposição de que o método A. foo tem implementação única e precisa ter seu código compilado invalidado.
  • Desotimização - Para código compilado ao vivo que está na pilha, a desotimização será iniciada para forçar o código compilado invalidado no modo intérprete para garantir a correção. Será utilizado um novo mecanismo de desotimização que é um híbrido de desotimização síncrona e assíncrona.

Caches embutidos em arquivos .oat

O ART agora emprega caches embutidos e otimiza os locais de chamada para os quais existem dados suficientes. O recurso de caches embutidos registra informações adicionais de tempo de execução em perfis e as utiliza para adicionar otimizações dinâmicas à compilação antecipada.

Dexlayout

Dexlayout é uma biblioteca introduzida no Android 8.0 para analisar arquivos dex e reordená-los de acordo com um perfil. Dexlayout visa usar informações de perfil de tempo de execução para reordenar seções do arquivo dex durante a compilação de manutenção ociosa no dispositivo. Ao agrupar partes do arquivo dex que são frequentemente acessadas juntas, os programas podem ter melhores padrões de acesso à memória a partir de uma localidade melhorada, economizando RAM e reduzindo o tempo de inicialização.

Como as informações de perfil estão disponíveis somente após a execução dos aplicativos, o dexlayout é integrado na compilação no dispositivo do dex2oat durante a manutenção ociosa.

Remoção de cache Dex

Até o Android 7.0, o objeto DexCache possuía quatro grandes arrays, proporcionais ao número de determinados elementos no DexFile, a saber:

  • strings (uma referência por DexFile::StringId),
  • tipos (uma referência por DexFile::TypeId),
  • métodos (um ponteiro nativo por DexFile::MethodId),
  • campos (um ponteiro nativo por DexFile::FieldId).

Esses arrays foram usados ​​para recuperação rápida de objetos que resolvemos anteriormente. No Android 8.0, todos os arrays foram removidos, exceto o array de métodos.

Desempenho do intérprete

O desempenho do intérprete melhorou significativamente na versão Android 7.0 com a introdução do "mterp" - um intérprete com um mecanismo central de busca/decodificação/interpretação escrito em linguagem assembly. Mterp é modelado a partir do interpretador rápido Dalvik e suporta arm, arm64, x86, x86_64, mips e mips64. Para código computacional, o mterp de Art é aproximadamente comparável ao interpretador rápido de Dalvik. No entanto, em algumas situações pode ser significativamente - e até dramaticamente - mais lento:

  1. Invoque o desempenho.
  2. Manipulação de strings e outros usuários pesados ​​de métodos reconhecidos como intrínsecos em Dalvik.
  3. Maior uso de memória de pilha.

O Android 8.0 resolve esses problemas.

Mais inlining

Desde o Android 6.0, o ART pode incorporar qualquer chamada nos mesmos arquivos dex, mas só pode incorporar métodos folha de diferentes arquivos dex. Houve duas razões para esta limitação:

  1. O inlining de outro arquivo dex requer o uso do cache dex desse outro arquivo dex, ao contrário do mesmo inlining do arquivo dex, que poderia apenas reutilizar o cache dex do chamador. O cache dex é necessário no código compilado para algumas instruções, como chamadas estáticas, carregamento de string ou carregamento de classe.
  2. Os mapas de pilha codificam apenas um índice de método dentro do arquivo dex atual.

Para resolver essas limitações, o Android 8.0:

  1. Remove o acesso ao cache dex do código compilado (consulte também a seção "Remoção do cache Dex")
  2. Estende a codificação do mapa de pilha.

Melhorias de sincronização

A equipe ART ajustou os caminhos de código MonitorEnter/MonitorExit e reduziu nossa dependência de barreiras de memória tradicionais no ARMv8, substituindo-as por instruções mais recentes (adquirir/liberar) sempre que possível.

Métodos nativos mais rápidos

Chamadas nativas mais rápidas para Java Native Interface (JNI) estão disponíveis usando as anotações @FastNative e @CriticalNative . Essas otimizações de tempo de execução ART integradas aceleram as transições JNI e substituem a agora obsoleta notação !bang JNI . As anotações não têm efeito em métodos não nativos e estão disponíveis apenas para o código da linguagem Java da plataforma no bootclasspath (sem atualizações da Play Store).

A anotação @FastNative oferece suporte a métodos não estáticos. Use isto se um método acessar um jobject como parâmetro ou valor de retorno.

A anotação @CriticalNative fornece uma maneira ainda mais rápida de executar métodos nativos, com as seguintes restrições:

  • Os métodos devem ser estáticos – sem objetos para parâmetros, valores de retorno ou um this implícito.
  • Apenas tipos primitivos são passados ​​para o método nativo.
  • O método nativo não utiliza os parâmetros JNIEnv e jclass em sua definição de função.
  • O método deve ser registrado com RegisterNatives em vez de depender de vinculação JNI dinâmica.

@FastNative pode melhorar o desempenho do método nativo em até 3x e @CriticalNative em até 5x. Por exemplo, uma transição JNI medida em um dispositivo Nexus 6P:

Invocação da Interface Nativa Java (JNI) Tempo de execução (em nanossegundos)
JNI normal 115
!bang JNI 60
@FastNative 35
@CriticalNative 25