Améliorations d'Android 8.0 ART

Le runtime Android (ART) a été considérablement amélioré dans la version Android 8.0. La liste ci-dessous résume les améliorations auxquelles les fabricants d’appareils peuvent s’attendre dans ART.

Récupérateur de place à compactage simultané

Comme annoncé lors de Google I/O, ART propose un nouveau garbage collector (GC) à compactage simultané dans Android 8.0. Ce collecteur compacte le tas à chaque fois que GC s'exécute et pendant l'exécution de l'application, avec une seule courte pause pour le traitement des racines des threads. Voici ses avantages :

  • GC compacte toujours le tas : des tailles de tas 32 % plus petites en moyenne par rapport à Android 7.0.
  • Le compactage permet l'allocation d'objets de pointeur de bosse local de thread : les allocations sont 70 % plus rapides que dans Android 7.0.
  • Offre des temps de pause 85 % plus petits pour le benchmark H2 par rapport à Android 7.0 GC.
  • Les temps de pause ne s'adaptent plus à la taille du tas ; les applications devraient pouvoir utiliser de gros tas sans se soucier des erreurs.
  • Détails de la mise en œuvre du GC - Lire les obstacles :
    • Les barrières de lecture représentent une petite quantité de travail effectué pour chaque lecture de champ d’objet.
    • Ceux-ci sont optimisés dans le compilateur, mais peuvent ralentir certains cas d'utilisation.

Optimisations de boucle

Une grande variété d'optimisations de boucles sont utilisées par ART dans la version Android 8.0 :

  • Éliminations du contrôle des limites
    • Statique : il est prouvé que les plages sont dans les limites au moment de la compilation
    • Dynamique : les tests d'exécution garantissent que les boucles restent dans les limites (sinon, désactivez)
  • Éliminations des variables d'induction
    • Supprimer l'induction morte
    • Remplacer l'induction utilisée uniquement après la boucle par des expressions de forme fermée
  • Élimination du code mort à l'intérieur du corps de la boucle, suppression des boucles entières qui deviennent mortes
  • Réduction de la force
  • Transformations de boucles : inversion, interchangement, fractionnement, déroulement, unimodulaire, etc.
  • SIMDisation (également appelée vectorisation)

L'optimiseur de boucle réside dans sa propre passe d'optimisation dans le compilateur ART. La plupart des optimisations de boucles sont similaires aux optimisations et simplifications ailleurs. Des défis surviennent avec certaines optimisations qui réécrivent le CFG d'une manière plus élaborée que d'habitude, car la plupart des utilitaires CFG (voir nodes.h) se concentrent sur la construction d'un CFG, pas sur sa réécriture.

Analyse de la hiérarchie des classes

ART dans Android 8.0 utilise Class Hierarchy Analysis (CHA), une optimisation du compilateur qui dévirtualise les appels virtuels en appels directs en fonction des informations générées par l'analyse des hiérarchies de classes. Les appels virtuels sont coûteux car ils sont implémentés autour d’une recherche de table virtuelle et nécessitent quelques charges dépendantes. De plus, les appels virtuels ne peuvent pas être intégrés.

Voici un résumé des améliorations associées :

  • Mise à jour dynamique de l'état de la méthode à implémentation unique - À la fin du temps de liaison des classes, lorsque la table virtuelle a été remplie, ART effectue une comparaison entrée par entrée avec la table virtuelle de la super classe.
  • Optimisation du compilateur - Le compilateur tirera parti des informations d'implémentation unique d'une méthode. Si une méthode A.foo a un indicateur d'implémentation unique, le compilateur dévirtualisera l'appel virtuel en un appel direct et tentera en outre d'intégrer l'appel direct en conséquence.
  • Invalidation du code compilé - Également à la fin du temps de liaison des classes lorsque les informations d'implémentation unique sont mises à jour, si la méthode A.foo qui avait auparavant une implémentation unique mais que ce statut est maintenant invalidé, tout le code compilé qui dépend de l'hypothèse que la méthode A. foo a besoin d'une implémentation unique pour que son code compilé soit invalidé.
  • Désoptimisation - Pour le code compilé en direct sur la pile, la désoptimisation sera lancée pour forcer le code compilé invalidé en mode interpréteur afin de garantir l'exactitude. Un nouveau mécanisme de désoptimisation hybride de désoptimisation synchrone et asynchrone sera utilisé.

Caches en ligne dans les fichiers .oat

ART utilise désormais des caches en ligne et optimise les sites d'appel pour lesquels suffisamment de données existent. La fonctionnalité de cache en ligne enregistre des informations d'exécution supplémentaires dans les profils et les utilise pour ajouter des optimisations dynamiques à la compilation anticipée.

Disposition Dex

Dexlayout est une bibliothèque introduite dans Android 8.0 pour analyser les fichiers dex et les réorganiser selon un profil. Dexlayout vise à utiliser les informations de profilage d'exécution pour réorganiser les sections du fichier dex lors de la compilation de maintenance inactive sur l'appareil. En regroupant les parties du fichier dex qui sont souvent consultées ensemble, les programmes peuvent avoir de meilleurs modèles d'accès à la mémoire grâce à une localité améliorée, économisant ainsi de la RAM et réduisant le temps de démarrage.

Étant donné que les informations de profil ne sont actuellement disponibles qu'après l'exécution des applications, dexlayout est intégré à la compilation sur l'appareil de dex2oat pendant la maintenance inactive.

Suppression du cache Dex

Jusqu'à Android 7.0, l'objet DexCache possédait quatre grands tableaux, proportionnels au nombre de certains éléments dans le DexFile, à savoir :

  • chaînes (une référence par DexFile::StringId),
  • types (une référence par DexFile::TypeId),
  • méthodes (un pointeur natif par DexFile::MethodId),
  • champs (un pointeur natif par DexFile::FieldId).

Ces tableaux ont été utilisés pour une récupération rapide des objets que nous avons précédemment résolus. Dans Android 8.0, tous les tableaux ont été supprimés à l'exception du tableau des méthodes.

Performance de l'interprète

Les performances de l'interpréteur se sont considérablement améliorées dans la version Android 7.0 avec l'introduction de "mterp" - un interpréteur doté d'un mécanisme de récupération/décodage/interprétation de base écrit en langage assembleur. Mterp est calqué sur l'interpréteur rapide Dalvik et prend en charge arm, arm64, x86, x86_64, mips et mips64. Pour le code informatique, le mterp d'Art est à peu près comparable à l'interpréteur rapide de Dalvik. Cependant, dans certaines situations, cela peut être considérablement – ​​et même considérablement – ​​plus lent :

  1. Invoquez les performances.
  2. Manipulation de chaînes et autres grands utilisateurs de méthodes reconnues comme intrinsèques à Dalvik.
  3. Utilisation plus élevée de la mémoire de la pile.

Android 8.0 résout ces problèmes.

Plus d'inline

Depuis Android 6.0, ART peut intégrer n'importe quel appel dans les mêmes fichiers dex, mais ne peut intégrer que des méthodes feuille à partir de différents fichiers dex. Il y avait deux raisons à cette limitation :

  1. L'insertion à partir d'un autre fichier dex nécessite d'utiliser le cache dex de cet autre fichier dex, contrairement à l'insertion du même fichier dex, qui pourrait simplement réutiliser le cache dex de l'appelant. Le cache dex est nécessaire dans le code compilé pour quelques instructions telles que les appels statiques, le chargement de chaînes ou le chargement de classes.
  2. Les cartes de pile codent uniquement un index de méthode dans le fichier dex actuel.

Pour remédier à ces limitations, Android 8.0 :

  1. Supprime l'accès au cache Dex du code compilé (voir également la section "Suppression du cache Dex")
  2. Étend le codage de la carte de pile.

Améliorations de la synchronisation

L'équipe ART a ajusté les chemins de code MonitorEnter/MonitorExit et a réduit notre dépendance aux barrières de mémoire traditionnelles sur ARMv8, en les remplaçant par des instructions plus récentes (acquisition/libération) lorsque cela est possible.

Méthodes natives plus rapides

Des appels natifs plus rapides vers Java Native Interface (JNI) sont disponibles à l’aide des annotations @FastNative et @CriticalNative . Ces optimisations d'exécution ART intégrées accélèrent les transitions JNI et remplacent la notation !bang JNI désormais obsolète. Les annotations n'ont aucun effet sur les méthodes non natives et ne sont disponibles que pour le code du langage Java de la plate-forme sur le bootclasspath (pas de mise à jour du Play Store).

L'annotation @FastNative prend en charge les méthodes non statiques. Utilisez-le si une méthode accède à un jobject en tant que paramètre ou valeur de retour.

L'annotation @CriticalNative fournit un moyen encore plus rapide d'exécuter des méthodes natives, avec les restrictions suivantes :

  • Les méthodes doivent être statiques : aucun objet pour les paramètres, les valeurs de retour ou un this implicite.
  • Seuls les types primitifs sont transmis à la méthode native.
  • La méthode native n'utilise pas les paramètres JNIEnv et jclass dans sa définition de fonction.
  • La méthode doit être enregistrée auprès de RegisterNatives au lieu de s'appuyer sur une liaison JNI dynamique.

@FastNative peut améliorer les performances des méthodes natives jusqu'à 3x et @CriticalNative jusqu'à 5x. Par exemple, une transition JNI mesurée sur un appareil Nexus 6P :

Appel de l'interface native Java (JNI) Temps d'exécution (en nanosecondes)
JNI régulier 115
!bang JNI 60
@FastNative 35
@CriticalNative 25