Miglioramenti ad Android 8.0 ART

Il runtime Android (ART) è stato migliorato significativamente nella versione Android 8.0. L'elenco seguente riassume i miglioramenti che i produttori di dispositivi possono aspettarsi da ART.

Garbage Collector di compattazione simultanea

Come annunciato al Google I/O, ART presenta un nuovo garbage collector (GC) a compressione simultanea in Android 8.0. Questo raccoglitore compatta l'heap ogni volta che viene eseguito GC e mentre l'app è in esecuzione, con solo una breve pausa per l'elaborazione delle radici dei thread. Ecco i suoi vantaggi:

  • GC compatta sempre l'heap: dimensioni heap più piccole in media del 32% rispetto ad Android 7.0.
  • La compattazione consente l'allocazione di oggetti puntatore bump locale del thread: le allocazioni sono più veloci del 70% rispetto ad Android 7.0.
  • Offre tempi di pausa inferiori dell'85% per il benchmark H2 rispetto ad Android 7.0 GC.
  • I tempi di pausa non si adattano più alla dimensione dell'heap; le app dovrebbero essere in grado di utilizzare heap di grandi dimensioni senza preoccuparsi di jank.
  • Dettagli di implementazione GC - Lettura delle barriere:
    • Le barriere di lettura sono una piccola quantità di lavoro svolto per ciascun campo oggetto letto.
    • Questi sono ottimizzati nel compilatore, ma potrebbero rallentare alcuni casi d'uso.

Ottimizzazioni del ciclo

ART nella versione Android 8.0 impiega un'ampia varietà di ottimizzazioni del loop:

  • I limiti controllano le eliminazioni
    • Statico: è dimostrato che gli intervalli rientrano nei limiti in fase di compilazione
    • Dinamico: i test in fase di esecuzione garantiscono che i loop rimangano entro i limiti (deopt altrimenti)
  • Eliminazioni delle variabili di induzione
    • Rimuovere l'induzione morta
    • Sostituisci l'induzione utilizzata solo dopo il ciclo con espressioni in forma chiusa
  • Eliminazione del codice morto all'interno del corpo del loop, rimozione di interi loop che diventano morti
  • Riduzione della forza
  • Trasformazioni del loop: inversione, interscambio, suddivisione, svolgimento, unimodulare, ecc.
  • SIMDizzazione (chiamata anche vettorizzazione)

L'ottimizzatore del loop risiede nel proprio passaggio di ottimizzazione nel compilatore ART. La maggior parte delle ottimizzazioni del ciclo sono simili alle ottimizzazioni e alla semplificazione altrove. Le sfide sorgono con alcune ottimizzazioni che riscrivono il CFG in un modo più elaborato del solito, perché la maggior parte delle utilità CFG (vedi nodes.h) si concentra sulla costruzione di un CFG, non sulla riscrittura di uno.

Analisi della gerarchia delle classi

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

Ecco un riepilogo dei miglioramenti correlati:

  • Aggiornamento dinamico dello stato del metodo di implementazione singola: al termine del collegamento della classe, quando vtable è stato popolato, ART esegue un confronto voce per voce con la vtable della super classe.
  • Ottimizzazione del compilatore: il compilatore trarrà vantaggio dalle informazioni di implementazione singola di un metodo. Se un metodo A.foo ha un flag di implementazione singola impostato, il compilatore devirtutualizzerà la chiamata virtuale in una chiamata diretta e di conseguenza tenterà ulteriormente di incorporare la chiamata diretta.
  • Invalidazione del codice compilato: anche alla fine del tempo di collegamento della classe quando vengono aggiornate le informazioni sull'implementazione singola, se il metodo A.foo che in precedenza aveva un'implementazione singola ma tale stato è ora invalidato, tutto il codice compilato che dipende dal presupposto che il metodo A. foo ha necessità di implementazione singola per invalidare il codice compilato.
  • Deottimizzazione: per il codice compilato in tempo reale nello stack, verrà avviata la deottimizzazione per forzare il codice compilato invalidato in modalità interprete per garantire la correttezza. Verrà utilizzato un nuovo meccanismo di deottimizzazione che è un ibrido di deottimizzazione sincrona e asincrona.

Cache in linea nei file .oat

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

Layout destro

Dexlayout è una libreria introdotta in Android 8.0 per analizzare i file dex e riordinarli in base a un profilo. Dexlayout mira a utilizzare le informazioni di profilazione di runtime per riordinare le sezioni del file dex durante la compilazione della manutenzione inattiva sul dispositivo. Raggruppando parti del file dex a cui spesso si accede insieme, i programmi possono avere modelli di accesso alla memoria migliori da una località migliorata, risparmiando RAM e riducendo i tempi di avvio.

Poiché le informazioni del profilo sono attualmente disponibili solo dopo l'esecuzione delle app, dexlayout è integrato nella compilazione sul dispositivo di dex2oat durante la manutenzione inattiva.

Rimozione della cache Dex

Fino ad Android 7.0, l'oggetto DexCache possedeva quattro grandi array, proporzionali al numero di determinati elementi nel DexFile, vale a dire:

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

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

Prestazioni dell'interprete

Le prestazioni dell'interprete sono migliorate in modo significativo nella versione Android 7.0 con l'introduzione di "mterp", un interprete dotato di un meccanismo di recupero/decodifica/interpretazione di base scritto in linguaggio assembly. Mterp è modellato sul veloce interprete Dalvik e supporta arm, arm64, x86, x86_64, mips e mips64. Per il codice computazionale, l'mterp di Art è più o meno paragonabile all'interprete veloce di Dalvik. Tuttavia, in alcune situazioni può essere notevolmente, e persino notevolmente, più lento:

  1. Invocare le prestazioni.
  2. Manipolazione delle stringhe e altri utilizzatori assidui di metodi riconosciuti come intrinseci in Dalvik.
  3. Maggiore utilizzo della memoria dello stack.

Android 8.0 risolve questi problemi.

Più in linea

A partire da Android 6.0, ART può incorporare qualsiasi chiamata all'interno degli stessi file dex, ma può solo incorporare metodi foglia da file dex diversi. Le ragioni di questa limitazione erano due:

  1. L'incorporamento da un altro file dex richiede l'utilizzo della cache dex di quell'altro file dex, a differenza dell'inlining dello stesso file dex, che potrebbe semplicemente riutilizzare la cache dex del chiamante. La cache dex è necessaria nel codice compilato per un paio di istruzioni come chiamate statiche, caricamento di stringhe o caricamento di classi.
  2. Le mappe dello stack codificano solo un indice del 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 cache dex")
  2. Estende la codifica della mappa dello stack.

Miglioramenti alla sincronizzazione

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

Metodi nativi più veloci

Chiamate native più veloci a Java Native Interface (JNI) sono disponibili utilizzando le annotazioni @FastNative e @CriticalNative . Queste ottimizzazioni di runtime ART integrate accelerano le transizioni JNI e sostituiscono la notazione JNI !bang, ora deprecata. Le annotazioni non hanno effetto sui metodi non nativi e sono disponibili solo per il codice Java Language della piattaforma nel bootclasspath (nessun aggiornamento del Play Store).

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

L'annotazione @CriticalNative fornisce un modo ancora più veloce per eseguire metodi nativi, con le seguenti restrizioni:

  • I metodi devono essere statici: nessun oggetto 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é fare affidamento sul collegamento JNI dinamico.

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

Invocazione dell'interfaccia nativa Java (JNI). Tempo di esecuzione (in nanosecondi)
JNI regolare 115
!bang JNI 60
@FastNative 35
@CriticalNative 25