Melhorias no ART do Android 8.0

O Android Runtime (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 tem um novo coletor de lixo (GC) compacto e simultâneo no Android 8.0. Esse coletor compacta o heap sempre que o GC é executado e enquanto o app está em execução, com apenas uma pequena pausa para processar raízes de linhas de execução. Confira os benefícios:

  • A coleta de lixo 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 incremento local da linha de execução: as alocações são 70% mais rápidas do que no Android 7.0.
  • Oferece tempos de pausa 85% menores para o comparativo de H2 em comparação com o GC do Android 7.0.
  • Os tempos de pausa não são mais dimensionados com o tamanho do heap. Os apps podem usar heaps grandes sem se preocupar com instabilidade.
  • Detalhe da implementação de GC: barreiras de leitura:
    • As barreiras de leitura são uma pequena quantidade de trabalho feita para cada leitura de campo de objeto.
    • Eles são otimizados no compilador, mas podem diminuir a velocidade de alguns casos de uso.

Otimizações de loop

O ART usa uma grande variedade de otimizações de loop na versão do Android 8.0:

  • Eliminações de verificação de limites
    • Estática: os intervalos são comprovadamente dentro dos limites no momento da compilação
    • Dinâmico: os testes de 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
    • Remover indução morta
    • Substitua a indução usada apenas após o loop por expressões de forma fechada.
  • Eliminação de código inoperante dentro do corpo do loop e remoção de loops inteiros que se tornam inoperantes
  • Redução de força
  • Transformações de loop: inversão, troca, divisão, desenrolamento, unimodular etc.
  • SIMDização (também chamada de vetorização)

O otimizador de loop reside na própria transmissão de otimização no compilador ART. A maioria das otimizações de loop é semelhante às otimizações e simplificações em outros lugares. Algumas otimizações que reescrevem a CFG de uma maneira mais elaborada do que o normal causam problemas, porque a maioria dos utilitários de CFG (consulte nodes.h) se concentra na criação de uma CFG, não na reescrita.

Análise de hierarquia de classes

A ART no Android 8.0 usa a análise de hierarquia de classes (CHA, na sigla em inglês), uma otimização de 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 porque são implementadas em torno de uma pesquisa de vtable e levam alguns carregamentos dependentes. Além disso, as chamadas virtuais não podem ser inlines.

Confira 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 é preenchida, o ART faz uma comparação entrada por entrada com a vtable da superclasse.
  • Otimização do compilador: o compilador aproveita as informações de implementação única de um método. Se um método A.foo tiver uma flag de implementação única definida, o compilador vai desvirtualizar a chamada virtual em uma chamada direta e tentar inserir 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 antes tinha implementação única, mas esse status agora está invalidado, todo o código compilado que depende da proposição de que o método A.foo tem implementação única precisa ser invalidado.
  • Desotimização: para código compilado ativo na pilha, a desotimização será iniciada para forçar o código compilado invalidado ao modo de intérprete e garantir a correção. Um novo mecanismo de remoção de otimização, que é um híbrido de remoção de otimização síncrona e assíncrona, será usado.

Caches in-line em arquivos .oat

Agora, a ART usa caches inline e otimiza os sites de chamada para os quais há dados suficientes. O recurso de caches inline registra informações adicionais de tempo de execução em perfis e as usa para adicionar otimizações dinâmicas à compilação antecipada.

Dexlayout

O Dexlayout é uma biblioteca introduzida no Android 8.0 para analisar arquivos dex e reordená-los de acordo com um perfil. O Dexlayout usa informações de criação 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 acessadas com frequência, os programas podem ter padrões de acesso à memória melhores devido à localidade aprimorada, economizando RAM e reduzindo o tempo de inicialização.

Como as informações de perfil só estão disponíveis depois que os apps são executados, o dexlayout é integrado à compilação no dispositivo do dex2oat durante a manutenção ociosa.

Remoção do cache Dex

Até o Android 7.0, o objeto DexCache tinha quatro grandes matrizes, proporcionais ao número de determinados elementos no DexFile, ou seja:

  • 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, todas as matrizes foram removidas, exceto a matriz de métodos.

Performance do intérprete

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

  1. Invocar a performance.
  2. Manipulação de strings e outros usuários pesados de métodos reconhecidos como intrínsecos no Dalvik.
  3. Maior uso de memória da pilha.

O Android 8.0 resolve esses problemas.

Mais inlining

Desde o Android 6.0, o ART pode inserir qualquer chamada nos mesmos arquivos dex, mas só pode inserir métodos de folha de arquivos dex diferentes. Havia dois motivos para essa limitação:

  1. A inclusão inline de outro arquivo dex exige o uso do cache dex desse outro arquivo dex, ao contrário da inclusão inline do mesmo arquivo dex, que pode apenas reutilizar o cache dex do caller. O cache dex é necessário no código compilado para algumas instruções, como chamadas estáticas, carregamento de strings ou carregamento de classes.
  2. Os mapas de pilha codificam apenas um índice de método no 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 na sincronização

A equipe do 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 (aquisição/liberação) 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 integradas do tempo de execução do ART aceleram as transições da JNI e substituem a notação !bang JNI, que agora está descontinuada. As anotações não têm efeito em métodos não nativos e estão disponíveis apenas para código da linguagem Java da plataforma no bootclasspath (sem atualizações da Play Store).

A anotação @FastNative é compatível com 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 oferece uma maneira ainda mais rápida de executar métodos nativos, com as seguintes restrições:

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

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

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