Miglioramenti di ART per Android 8.0

Android Runtime (ART) è stato migliorato in modo significativo nella release di Android 8.0. L'elenco seguente riassume i miglioramenti che i produttori di dispositivi possono aspettarsi in ART.

Garbage collector compattatore simultaneo

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

  • GC compatta sempre l'heap: dimensioni dell'heap in media più piccole del 32% rispetto ad Android 7.0.
  • La compattazione consente l'allocazione di oggetti puntatori di bump locali del thread: le allocazioni sono il 70% più veloci rispetto ad Android 7.0.
  • Offre tempi di pausa inferiori dell'85% per il benchmark H2 rispetto a GC Android 7.0.
  • I tempi di pausa non vengono più scalati in base alle dimensioni dell'heap; le app devono essere in grado di utilizzare heap di grandi dimensioni senza problemi di jank.
  • Dettaglio di implementazione di GC - Barriere di lettura:
    • Le barriere di lettura sono una piccola quantità di lavoro svolto per ogni campo dell'oggetto letto.
    • Questi vengono ottimizzati nel compilatore, ma potrebbero rallentare alcuni casi d'uso.

Ottimizzazioni dei loop

ART utilizza un'ampia gamma di ottimizzazioni dei loop nella release Android 8.0:

  • Eliminazioni del controllo dei limiti
    • Statica: gli intervalli sono dimostrati essere entro i limiti in fase di compilazione
    • Dinamico: i test in fase di esecuzione garantiscono che i loop rimangano entro i limiti (in caso contrario, viene eseguita l'ottimizzazione)
  • Eliminazioni di variabili di induzione
    • Rimuovi induzione non riuscita
    • Sostituisci l'induzione utilizzata solo dopo il ciclo con espressioni in forma chiusa
  • Eliminazione del codice inutilizzato all'interno del corpo del ciclo, rimozione di interi cicli che diventano inutilizzati
  • Riduzione della forza
  • Trasformazioni del ciclo: inversione, scambio, suddivisione, srotolamento, unimodulare e così via.
  • SIMDizzazione (chiamata anche vettorizzazione)

L'ottimizzatore di loop si trova nel proprio passaggio di ottimizzazione nel compilatore ART. La maggior parte delle ottimizzazioni del ciclo sono simili alle ottimizzazioni e alla semplificazione altrove. Si presentano 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 delle classi

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 perché vengono implementate intorno a una ricerca nella vtable e richiedono un paio di caricamenti dipendenti. Inoltre, le chiamate virtuali non possono essere incorporate.

Ecco un riepilogo dei miglioramenti correlati:

  • Aggiornamento dinamico dello stato del metodo di implementazione singola: alla fine del tempo di 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 sfrutterà le informazioni sull'implementazione singola di un metodo. Se un metodo A.foo ha il flag di implementazione singola impostato, il compilatore devirtualizzerà la chiamata virtuale in una chiamata diretta e cercherà ulteriormente di incorporare la chiamata diretta.
  • Invalidazione del codice compilato: anche alla fine del periodo di collegamento delle classi, quando vengono aggiornate le informazioni di implementazione singola, se il metodo A.foo aveva in precedenza un'implementazione singola, ma questo stato è ora invalidato, tutto il codice compilato che dipende dal presupposto che il metodo A.foo abbia un'implementazione singola deve essere invalidato.
  • Deottimizzazione: per il codice compilato live che si trova 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 ibrido di deottimizzazione sincrona e asincrona.

Cache inline nei file .oat

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

Dexlayout

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 del runtime per riordinare le sezioni del file dex durante la compilazione della manutenzione inattiva sul dispositivo. Raggruppando le parti del file dex a cui si accede spesso insieme, i programmi possono avere pattern di accesso alla memoria migliori grazie a 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 array di grandi dimensioni, proporzionali al numero di determinati elementi nel DexFile, ovvero:

  • 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 venivano utilizzati per il recupero rapido degli 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 notevolmente nella release di Android 7.0 con l'introduzione di "mterp", un interprete con un meccanismo di recupero/decodifica/interpretazione principale scritto in linguaggio assembly. Mterp è modellato sull'interprete Dalvik veloce e supporta arm, arm64, x86, x86_64, mips e mips64. Per il codice computazionale, mterp di Art è più o meno paragonabile all'interprete veloce di Dalvik. Tuttavia, in alcune situazioni può essere molto più lenta, anche in modo drastico:

  1. Richiamare il rendimento.
  2. Manipolazione di stringhe e altri utenti che utilizzano molto i metodi riconosciuti come intrinseci in Dalvik.
  3. Maggiore utilizzo della memoria dello stack.

Android 8.0 risolve questi problemi.

Più inlining

A partire da Android 6.0, ART può incorporare qualsiasi chiamata all'interno degli stessi file dex, ma può incorporare solo i metodi foglia di file dex diversi. Questa limitazione era dovuta a due motivi:

  1. L'incorporamento da un altro file dex richiede l'utilizzo della cache dex di quell'altro file dex, a differenza dell'incorporamento dello stesso file dex, che potrebbe 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 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 della cache Dex")
  2. Estende la codifica della mappa a barre.

Miglioramenti della sincronizzazione

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

Metodi nativi più veloci

Sono disponibili chiamate native più veloci alla Java Native Interface (JNI) utilizzando le annotazioni @FastNative e @CriticalNative. Queste ottimizzazioni integrate di ART Runtime velocizzano le transizioni JNI e sostituiscono la notazione !bang JNI ora ritirata. Le annotazioni non hanno alcun effetto sui metodi non nativi e sono disponibili solo per il codice Java della piattaforma su bootclasspath (nessun aggiornamento del Play Store).

L'annotazione @FastNative supporta metodi non statici. Utilizza questo 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: nessun oggetto per parametri, valori restituiti o un this implicito.
  • Al metodo nativo vengono passati solo i tipi primitivi.
  • 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 il rendimento del metodo nativo fino a tre volte e @CriticalNative fino a cinque volte. Ad esempio, una transizione JNI misurata su un dispositivo Nexus 6P:

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