O tempo de execução do Android (ART) foi aprimorado significativamente na versão Android 8.0. A lista abaixo resume os aprimoramentos 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 (GC) de compactação simultânea no Android 8.0. Esse coletor compacta o heap toda vez 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 do ponteiro de aumento local do 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 instabilidade.
- Detalhe da implementação do GC - Barreiras de leitura:
- 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 desacelerar 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: provou-se que os intervalos estão dentro dos limites em tempo de 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 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, intercâmbio, 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. Surgem desafios com algumas otimizações que reescrevem o CFG de uma maneira mais elaborada do que o normal, porque a maioria dos utilitários de CFG (consulte nodes.h) se concentra na construção de um CFG, não em reescrever um.
Análise da hierarquia de classes
O ART no Android 8.0 usa Class Hierarchy Analysis (CHA), 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, pois são implementadas em torno de uma pesquisa vtable e levam algumas cargas dependentes. Além disso, 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 de 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á inline a chamada direta.
- 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 o 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 exatidão. Será utilizado um novo mecanismo de desoptimização que é um híbrido de desoptimização síncrona e assíncrona.
Caches embutidos em arquivos .oat
O ART agora emprega caches em linha e otimiza os locais de chamada para os quais existem dados suficientes. O recurso de caches embutidos registra informações de tempo de execução adicionais 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 aprimorada, economizando RAM e reduzindo o tempo de inicialização.
Como as informações do perfil estão disponíveis apenas 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 arrays grandes, 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).
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 interpretador com um mecanismo central de busca/decodificação/interpretação escrito em linguagem assembly. Mterp é modelado após o interpretador Dalvik rápido 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:
- Invoque o desempenho.
- Manipulação de strings e outros usuários pesados de métodos reconhecidos como intrínsecos em Dalvik.
- Maior uso de memória de pilha.
O Android 8.0 aborda esses problemas.
Mais inlining
Desde o Android 6.0, o ART pode incorporar qualquer chamada dentro dos mesmos arquivos dex, mas só poderia incorporar métodos leaf de diferentes arquivos dex. Havia duas razões para essa limitação:
- O inlining de outro arquivo dex requer o uso do cache dex desse outro arquivo dex, ao contrário do inlining do mesmo 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.
- Os mapas de pilha estão apenas codificando um índice de método dentro do arquivo dex atual.
Para lidar com essas limitações, o Android 8.0:
- Remove o acesso ao cache dex do código compilado (consulte também a seção "Remoção do cache dex")
- Estende a codificação do mapa de pilha.
Melhorias na 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 a Java Native Interface (JNI) estão disponíveis usando as anotações @FastNative
e @CriticalNative
. Essas otimizações integradas de tempo de execução do ART 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 um 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. - Somente tipos primitivos são passados para o método nativo.
- O método nativo não usa os parâmetros
JNIEnv
ejclass
em sua definição de função. - O método deve ser registrado com
RegisterNatives
em vez de depender da 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 |