Miglioramenti di ART per Android 8.0

Il runtime di Android (ART) è stato migliorato notevolmente nella release Android 8.0. L'elenco riportato di seguito riassume i miglioramenti che i produttori di dispositivi possono aspettarsi in ART.

Garbage collector con compressione concorrente

Come annunciato in occasione della conferenza Google I/O, ART include un nuovo garbage collector (GC) con compressione concorrente in Android 8.0. Questo raccoglitore compatta l'heap ogni volta che viene eseguito il GC e mentre l'app è in esecuzione, con una sola breve interruzione per l'elaborazione delle radici del thread. Ecco i vantaggi:

  • Il GC compatta sempre l'heap: in media le dimensioni dell'heap sono inferiori del 32% rispetto ad Android 7.0.
  • La compattazione consente l'allocazione di oggetti bump pointer locali del thread: le allocazioni sono il 70% più veloci rispetto ad Android 7.0.
  • Offre tempi di interruzione inferiori dell'85% per il benchmark H2 rispetto al GC di Android 7.0.
  • I tempi di pausa non variano più in base alle dimensioni dell'heap. Le app dovrebbero essere in grado di utilizzare heap di grandi dimensioni senza preoccuparsi di problemi di aggiornamento.
  • Dettagli sull'implementazione del GC: barriere di lettura:
    • Le barriere di lettura sono una piccola quantità di lavoro eseguita per ogni lettura del campo dell'oggetto.
    • Questi sono ottimizzati nel compilatore, ma potrebbero rallentare alcuni casi d'uso.

Ottimizzazioni del loop

Nella release Android 8.0, ART utilizza una vasta gamma di ottimizzazioni dei loop:

  • Eliminazioni dei controlli dei limiti
    • Statico: gli intervalli sono dimostrati essere entro i limiti in fase di compilazione
    • Dinamico: i test di runtime assicurano che i loop rimangano nei limiti (in caso contrario, viene eseguita la disattivazione)
  • Eliminazioni di variabili di induzione
    • Rimuovi l'induzione non utilizzata
    • Sostituisci l'induzione utilizzata solo dopo il ciclo con espressioni chiuse
  • Eliminazione del codice inutilizzato all'interno del corpo del ciclo, rimozione di interi cicli che diventano inutili
  • Riduzione della resistenza
  • Trasformazioni dei loop: inversione, scambio, suddivisione, scollegamento, unimodulare e così via.
  • SIMDizzazione (chiamata anche vettorizzazione)

L'ottimizzatore del loop risiede nel proprio passaggio di ottimizzazione nel compilatore ART. La maggior parte delle ottimizzazioni dei loop è simile alle ottimizzazioni e alle semplificazioni altrove. Si verificano problemi con alcune ottimizzazioni che riscrivono il CFG in modo più elaborato del solito, perché la maggior parte delle utilità CFG (vedi nodes.h) si concentra sulla creazione di un CFG, non sulla sua riscrittura.

Analisi della gerarchia dei corsi

ART in Android 8.0 utilizza l'analisi della gerarchia delle classi (CHA), un'ottimizzazione del compilatore che devirtualizza le chiamate virtuali in chiamate dirette in base alle informazioni generate dall'analisi delle gerarchie delle classi. Le chiamate virtuali sono costose poiché vengono implementate in base a una ricerca nella vtable e richiedono un paio di carichi dipendenti. Inoltre, le chiamate virtuali non possono essere incorporate.

Ecco un riepilogo dei miglioramenti correlati:

  • Aggiornamento dello stato del metodo di implementazione singola dinamica: al termine del collegamento della classe, quando la vtable è stata compilata, ART esegue un confronto voce per voce con la vtable della superclasse.
  • Ottimizzazione del compilatore: il compilatore sfrutta le informazioni sull'implementazione singola di un metodo. Se per un metodo A.foo è impostato il flag di implementazione singola, il compilatore devirtualizzerà la chiamata virtuale in una chiamata diretta e tenterà di inserire in linea la chiamata diretta.
  • Annullamento dell'aggiornamento del codice compilato: anche al termine del tempo di collegamento del corso quando le informazioni sull'implementazione singola vengono aggiornate, se il metodo A.foo che in precedenza aveva un'implementazione singola, ma questo stato ora non è più valido, tutto il codice compilato che dipende dal presupposto che il metodo A.foo abbia un'implementazione singola deve avere il codice compilato invalidato.
  • Deottimizzazione: per il codice compilato in tempo reale presente nello stack, verrà avviata la deottimizzazione per forzare il codice compilato invalidato in modalità di interprete al fine di garantire la correttezza. Verrà utilizzato un nuovo meccanismo di deottimizzazione, ovvero un ibrido di deottimizzazione sincrona e asincrona.

Cache in linea nei file .oat

ART ora utilizza le cache in linea e ottimizza i siti di chiamata per i quali esistono dati sufficienti. La funzionalità delle cache in linea registra informazioni aggiuntive sul tempo di esecuzione nei profili e le utilizza per aggiungere ottimizzazioni dinamiche alla compilazione anticipata.

Dexlayout

Dexlayout è una libreria introdotta in Android 8.0 per analizzare i file dex e riordinarli in base a un profilo. Dexlayout ha lo scopo di utilizzare le informazioni di profilazione del runtime per riordinare le sezioni del file dex durante la compilazione di manutenzione inattiva sul dispositivo. Raggruppando le parti del file dex a cui viene spesso eseguito l'accesso contemporaneamente, i programmi possono avere pattern di accesso alla memoria migliori grazie a una maggiore località, risparmiando RAM e abbreviando il tempo di avvio.

Poiché le informazioni sul profilo sono attualmente disponibili solo dopo l'esecuzione delle app, dexlayout è integrato nella compilazione on-device di dex2oat durante la manutenzione in stato di inattività.

Rimozione della cache di Dex

Fino ad Android 7.0, l'oggetto DexCache possedeva quattro array di grandi dimensioni, proporzionali al numero di determinati elementi nel file Dex, ovvero:

  • stringhe (un riferimento per DexFile::StringId),
  • tipi (un riferimento per DexFile::TypeId),
  • metodi (un puntatore nativo per DexFile::MethodId),
  • (un puntatore nativo per DexFile::FieldId).

Questi array venivano utilizzati per il recupero rapido degli oggetti che precedentemente abbiamo risolto. In Android 8.0, sono stati rimossi tutti gli array tranne l'array di metodi.

Prestazioni dell'interprete

Le prestazioni dell'interprete sono migliorate notevolmente nella release di Android 7.0 con l'introduzione di "mterp", un interprete con un meccanismo di recupero/decodifica/interpretazione di base scritto in linguaggio assembly. Mterp è basato sull'interprete Dalvik veloce e supporta arm, arm64, x86, x86_64, mips e mips64. Per il codice di calcolo, mterp di Art è approssimativamente paragonabile all'interprete veloce di Dalvik. Tuttavia, in alcuni casi può essere molto più lento:

  1. Prestazioni di Invoke.
  2. Manipolazione di stringhe e altri utenti di metodi riconosciuti come intrinseci in Dalvik.
  3. Maggiore utilizzo della memoria dello stack.

Android 8.0 risolve questi problemi.

Più inlining

Da Android 6.0, ART può incorporare qualsiasi chiamata all'interno degli stessi file dex, ma poteva incorporare solo i metodi principali di file dex diversi. I motivi di questa limitazione erano due:

  1. L'inserimento in linea da un altro file dex richiede l'utilizzo della cache dex di quell'altro file dex, a differenza dell'inserimento in linea dello stesso file dex, che potrebbe semplicemente riutilizzare la cache dex del chiamante. La cache dex è necessaria nel codice compilato per alcune istruzioni come chiamate statiche, caricamento di stringhe o caricamento di classi.
  2. Le mappe dello stack codificano solo un indice di metodo all'interno del file dex corrente.

Per risolvere queste limitazioni, Android 8.0:

  1. Rimuove l'accesso alla cache dex dal codice compilato (vedi anche la sezione "Rimozione della cache dex")
  2. Estensione della codifica della mappa di stack.

Miglioramenti alla sincronizzazione

Il team ART ha ottimizzato i percorsi di codice MonitorEnter/MonitorExit e ha ridotto la nostra dipendenza dalle tradizionali barriere di memoria su ARMv8, sostituendole con istruzioni (acquire/release) più recenti, ove possibile.

Metodi nativi più rapidi

Le chiamate native più veloci all'interfaccia nativa Java (JNI) sono disponibili utilizzando le annotazioni @FastNative e @CriticalNative. Queste ottimizzazioni del runtime ART predefinite accelerano le transizioni JNI e sostituiscono la notazione !bang JNI ora deprecata. Le annotazioni non hanno alcun effetto sui metodi non nativi e sono disponibili solo per il codice in linguaggio Java della piattaforma su bootclasspath (nessun aggiornamento del Play Store).

L'annotazione @FastNative supporta i metodi non statici. Utilizzalo se un metodo accede a un jobject come parametro o valore restituito.

L'annotazione @CriticalNative offre un modo ancora più rapido per eseguire metodi nativi, con le seguenti limitazioni:

  • I metodi devono essere statici: non sono ammessi oggetti per parametri, valori restituiti o un this implicito.
  • Solo i tipi primitivi vengono passati al metodo nativo.
  • Il metodo nativo non utilizza i parametri JNIEnv e jclass nella definizione della funzione.
  • Il metodo deve essere registrato con RegisterNatives anziché basarsi sul collegamento JNI dinamico.

@FastNative può migliorare le prestazioni del metodo nativo fino a tre volte e @CriticalNative fino a cinque volte. Ad esempio, una transizione JNI misurata su un dispositivo Nexus 6P:

Invocazione di Java Native Interface (JNI) Tempo di esecuzione (in nanosecondi)
JNI normale 115
!bang JNI 60
@FastNative 35
@CriticalNative 25