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 no ART.

Coletor de lixo de compactação simultânea

Conforme anunciado no Google I/O, o ART apresenta um novo coletor de lixo de compactação (GC) simultâneo 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 as raízes do encadeamento. 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 objetos 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 heaps sem se preocupar com a instabilidade.
  • Detalhe da implementação do GC - Barreiras de leitura:
    • As barreiras de leitura são uma pequena quantidade de trabalho feito para cada campo de objeto lido.
    • Eles são otimizados no compilador, mas podem retardar alguns casos de uso.

Otimizações de loop

Uma ampla variedade de otimizações de loop é empregada 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 (deopt caso contrário)
  • 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 seu próprio passo de otimização no compilador ART. A maioria das otimizações de loop são semelhantes a otimizações e simplificações em outros lugares. Os desafios surgem com algumas otimizações que reescrevem o CFG de uma maneira mais elaborada do que o usual, porque a maioria dos utilitários de CFG (consulte nodes.h) se concentra na construção de um CFG, 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 vtable e levam algumas cargas dependentes. Também as chamadas virtuais não podem ser embutidas.

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 realiza uma comparação entrada por entrada com a vtable da superclasse.
  • Otimização do compilador - O compilador aproveitará as 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, como resultado, tentará inserir a chamada direta em linha.
  • 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 é invalidado, todo o código compilado que depende da suposição de que o método A. foo tem necessidades de implementação única para 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 de 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 em linha e otimiza os sites de chamadas para os quais existem dados suficientes. O recurso de caches embutidos registra informações adicionais de tempo de execução em perfis e as usa 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. O 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 acessados ​​juntos, os programas podem ter melhores padrões de acesso à memória a partir da localidade melhorada, economizando RAM e reduzindo o tempo de inicialização.

Como as informações de perfil estão disponíveis atualmente somente após a execução dos aplicativos, o dexlayout é integrado à 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 certos 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).

Essas matrizes foram usadas para recuperação rápida de objetos que resolvemos anteriormente. No Android 8.0, todas as matrizes foram removidas, exceto a matriz 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 principal de busca/decodificação/interpretação escrito em linguagem assembly. Mterp é modelado após o rápido interpretador 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. Invocar 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 aborda esses problemas.

Mais inline

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

  1. Inlining de outro arquivo dex requer o uso do cache dex desse outro arquivo dex, ao contrário do mesmo arquivo dex inlining, 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 estão apenas codificando 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/lançar) sempre que possível.

Métodos nativos mais rápidos

Chamadas nativas mais rápidas para a 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 suporta métodos não estáticos. Use isso 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 usa 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 de Java Native Interface (JNI) Tempo de execução (em nanossegundos)
JNI normal 115
!bang JNI 60
@FastNative 35
@CriticalNative 25