Le code que vous écrivez est lui-même une forme d'utilisation de la mémoire. Chaque classe, méthode et constante de chaîne de votre application doit être chargée dans la RAM lors de son exécution. Plus la base de code de votre application est grande, plus elle consomme de mémoire pour exister.
Mémoire soutenue par des fichiers et pagination à la demande
Android charge le code exécutable à partir de votre .apk (comme les fichiers .oat ou .so) à l'aide de mmap. Cela signifie que le code est associé à un fichier.
Il est important de noter qu'Android utilise la pagination à la demande. Lorsque votre application démarre, le noyau ne charge pas immédiatement l'intégralité de l'APK dans la RAM. Au lieu de cela, il ne fait que mapper le fichier dans l'espace d'adressage virtuel du processus. Lorsque votre application s'exécute et que le processeur passe à une nouvelle fonction, cela déclenche un "défaut de page". Le noyau met en pause le thread, lit cette page de code spécifique de 4 Ko à partir du stockage dans la RAM physique, puis reprend l'exécution.

Cela signifie que le code que vous packagez, mais que vous n'exécutez jamais, n'utilise pas de mémoire physique pour les pages de code elles-mêmes. Toutefois, les bibliothèques inutilisées augmentent toujours la taille globale de l'APK et peuvent augmenter considérablement la mémoire utilisée par les métadonnées internes du système (comme les index DEX et les descripteurs de classe), qui doivent être lues pour savoir que le code existe. De plus, de nombreuses bibliothèques contiennent des initialiseurs statiques ou sont utilisées par des frameworks d'injection de dépendances au démarrage de l'application, ce qui les fait de toute façon être paginées dans la RAM.
Éviction de pages et ralentissements
Comme la mémoire soutenue par des fichiers peut toujours être relue à partir du stockage, le noyau considère ces pages comme "propres". Lorsque le système est soumis à une pression de mémoire, le noyau évince (supprime) ces pages de code propre de la RAM pour faire de la place à d'autres éléments.
Si votre application doit exécuter à nouveau ce code ultérieurement, le processeur générera une erreur et le noyau devra relire la page à partir du stockage. Plus votre application contient de code, plus elle est susceptible d'être évincée. Lorsqu'un utilisateur revient à votre application volumineuse après avoir utilisé d'autres applications, il subit des à-coups et des ralentissements aléatoires, car le processeur est constamment à l'arrêt en attendant que le code soit repagé à partir du stockage.
Coût d'un défaut de page : bien qu'il varie considérablement en fonction de la vitesse de stockage de l'appareil (UFS ou eMMC) et de l'état du noyau, un défaut de page majeur (lecture de 4 Ko à partir du stockage) peut coûter entre 0,5 ms et 5 ms. Si votre chemin de démarrage touche 500 pages différentes de code non optimisé, vous pouvez facilement ajouter plusieurs centaines de millisecondes de latence d'E/S pure au temps de démarrage de votre application.
Explorer la taille du code avec Compiler Explorer
Pour mieux comprendre comment votre code Java ou Kotlin se traduit en code machine natif (et donc en octets de mémoire), vous pouvez utiliser Compiler Explorer.
La prise en charge d'Android est directement intégrée à Godbolt. Il vous permet de voir comment les différentes parties de la chaîne d'outils Android (D8, R8 et dex2oat) transforment votre code source.
Utiliser Compiler Explorer avec Android
- Accédez à godbolt.org.
- Sélectionnez Android Java ou Android Kotlin dans le menu déroulant des langages (en haut à gauche).
- Dans le menu déroulant du compilateur (en haut à droite du volet de code), vous pouvez choisir entre différents outils :
d8: affiche le bytecode Dalvik (.dex). Il s'agit de la représentation la plus proche de votre code d'origine, et elle est plus facile à lire.r8: montre comment l'optimiseur R8 réduit et optimise votre bytecode.dex2oat: affiche le code machine ARM64 final qui s'exécute réellement sur l'appareil. C'est ici que vous pouvez voir l'impact réel sur la mémoire (4 octets par instruction).dex2oatpeut cibler différentes ISA, mais ARM64 est la plus courante pour les téléphones mobiles.
- Mise en surbrillance de la source et de la sortie : lorsque vous pointez sur une ligne de code, les instructions de bytecode ou de code machine correspondantes sont mises en surbrillance, ce qui permet de suivre facilement l'impact d'instructions spécifiques.
- Pipeline d'optimisation : dans la vue de désassemblage, vous pouvez cliquer sur Ajouter nouveau... > Pipeline d'optimisation. Cela vous permet de voir les étapes internes suivies par le compilateur. Vous pouvez inspecter la façon dont la représentation interne est transformée à chaque étape (par exemple, entre les étapes "Inliner (avant)" et "Inliner (après)") avant d'être convertie en code machine ARM64 final.

Pourquoi est-ce important pour la mémoire ?
Chaque instruction que vous voyez dans la sortie dex2oat ciblant l'ISA ARM64 occupe 4 octets dans le fichier exécutable de votre application (.odex ou .oat).
Essayez de saisir du code qui utilise différentes fonctionnalités du langage et étudiez la sortie du compilateur :
- Accès aux tableaux vs itérateurs de liste :
- Une simple boucle de tableau sur
int[]peut être compilée en ~10 instructions (~40 octets). - Une boucle foreach sur un
Listutilise implicitement unIterator. Cela peut entraîner 30 à 40 instructions (environ 160 octets) en raison des appels de méthode supplémentaires (hasNext(),next()) et de l'allocation de l'objet itérateur lui-même. - Optimisation R8 : dans les bonnes conditions (par exemple, lorsque
Listest prouvé comme étant unArrayList), l'optimiseur R8 peut transformer une boucle foreach en une simple boucle indexée, ce qui élimine la surcharge de l'itérateur et réduit à la fois la taille du code et le churn de mémoire d'exécution.
- Une simple boucle de tableau sur
- Appels de méthode virtuelle : impliquent le chargement de la classe de l'objet, la recherche de la méthode dans
vtable, puis la création d'une branche. Cela prend généralement entre quatre et cinq instructions (environ 20 octets). - Appels directs/statiques : se traduisent souvent par une seule instruction
bl(branche avec lien) (4 octets). - Lambdas Kotlin : peuvent générer des classes anonymes entières et des méthodes de pont supplémentaires, ce qui ajoute des centaines d'octets de code et de métadonnées pour un simple bloc fonctionnel.
En utilisant Compiler Explorer, vous pouvez voir comment les fonctionnalités linguistiques sophistiquées (comme les lambdas Kotlin, les API de flux ou l'utilisation intensive de génériques) ont un impact sur la taille compilée finale de votre application, et comment les optimiseurs tels que R8 peuvent compenser le coût des abstractions linguistiques dans certains cas. Cet outil peut vous aider à faire des compromis éclairés lors de la conception et de l'implémentation d'une application.
En règle générale, plus le code de votre application est complexe, plus la mémoire utilisée est importante. À l'inverse, un code plus simple (ou simplifié par R8) se traduit par une représentation plus petite en termes d'instructions de processeur, d'octets de stockage et de RAM.
Mesurer l'impact du code avec meminfo et showmap
Vous pouvez utiliser les outils de mémoire Android standards pour voir la quantité de mémoire consommée par le code de votre application.
dumpsys meminfo
Lorsque vous exécutez adb shell dumpsys meminfo <package>, la catégorie Code de la section Résumé de l'application fournit une vue d'ensemble de la mémoire liée au code :
App Summary
Pss(KB)
------
Java Heap: 3244
Native Heap: 5412
Code: 24512 # <--- Sum of .so, .dex, .oat, .art, etc.
showmap
Pour une vue plus précise, utilisez showmap. Il révèle les régions de certains fichiers mappés à la mémoire.
adb shell showmap $(pidof <package>) | grep -E "\.oat|\.odex|\.dex|\.apk"
Vous verrez des entrées pour le code compilé de votre application :
size RSS PSS clean dirty clean dirty swap swapPSS object
------- -------- -------- -------- -------- -------- -------- -------- -------- ----------------
12288 8192 8192 8192 0 0 0 0 0 /data/app/.../base.odex
Code mort et R8
Étant donné que chaque méthode exécutée occupe de la mémoire, une application "gonflée" avec des initialisations inutiles ou des bibliothèques inutilisées peut avoir un impact considérable sur les performances de démarrage et l'utilisation de la mémoire de référence.
C'est pourquoi les outils tels que R8 (ProGuard) sont essentiels. R8 analyse le bytecode de votre application et supprime toutes les classes ou méthodes qui ne sont jamais appelées ("suppression du code mort").
Exercice pratique : le coût de l'inflation
Pour illustrer l'impact de la taille du code, prenons l'exemple d'un test comparant deux versions d'une application contenant 300 classes générées (chacune avec 500 méthodes) :
- CodeBloat (non optimisé) : compilation standard non optimisée contenant toutes les classes générées et les chaînes uniques.
- CodeBloatOptimized : même code source, mais compilé avec la minification R8 activée.
1. Compilation anticipée (AOT)
Pour maximiser l'impact de la mémoire soutenue par des fichiers, nous utiliserons l'outil cmd package compile pour compiler les applications en fichiers .oat à l'avance (AOT).
adb shell cmd package compile -m speed -f com.android.codebloat
adb shell cmd package compile -m speed -f com.android.codebloat.optimized
Veuillez noter qu'il s'agit d'un exemple synthétique. En règle générale, les applications utilisent le mode de compilation speed-profile (voir ci-dessous).
2. Lancer et comparer
Pour obtenir un véritable démarrage à froid où le système doit lire le code à partir du stockage, nous supprimons le cache de pages du noyau avant de lancer chaque application. Cela nécessite un accès root.
Lancez l'application non optimisée :
adb shell am force-stop com.android.codebloat
# Drop page cache to ensure the start is truly cold
adb shell "echo 3 > /proc/sys/vm/drop_caches"
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5 # Wait for the background thread to load classes
adb shell dumpsys meminfo -s com.android.codebloat
Faites de même pour l'application optimisée :
adb shell am force-stop com.android.codebloat.optimized
# Drop page cache to ensure the start is truly cold
adb shell "echo 3 > /proc/sys/vm/drop_caches"
adb shell am start -W -n com.android.codebloat.optimized/com.android.codebloat.MainActivity
sleep 5
adb shell dumpsys meminfo -s com.android.codebloat.optimized
Les résultats
Si vous examinez la ligne Code dans la section App Summary, vous constaterez une différence énorme :
- Non optimisé
Code: environ 30 000 Ko (30 Mo) - Optimisé
Code: environ 2 000 Ko (2 Mo)
Comme R8 a déterminé que les 500 méthodes à l'intérieur de ces classes n'étaient jamais réellement utiles (la méthode doSomething() n'appelle que method0(), et les résultats sont ignorés), il a supprimé la quasi-totalité du code généré artificiellement de l'APK final.
3. Voir l'impact dans Perfetto
L'impact de l'inflation du code est clairement visible lors de la phase de chargement initiale de l'application. Plus précisément, recherchez la tranche bindApplication sur le thread principal et les tranches imbriquées commençant par madvising, qui indiquent que le système se prépare à charger les fichiers de l'APK et son code compilé (.odex).
Lors d'un démarrage à froid interactif, le système mmap() et madvise() le code et les autres données de ces fichiers nécessaires au chargement et à l'exécution de l'application. La valeur après "size=" dans les tranches madvising indique la quantité de données à charger. Ce préchargement du code de l'application est effectué pour accélérer le démarrage de l'application.
La comparaison montre que la quantité de code d'application qui devait être chargée du stockage vers la RAM était beaucoup plus importante dans le cas de l'application gonflée, ce qui entraînait des durées plus longues qui contribuaient à un démarrage plus lent de l'application. De plus, la trace du démarrage de l'application volumineuse affiche des tranches pour le chargement des fichiers DEX secondaires (classes2.dex, classes3.dex) que l'application volumineuse a été forcée de "déverser" parce qu'elle ne tenait pas dans un seul fichier DEX.
Comparaison (démarrage à froid sur le Pixel 10a)
| Métrique | Non optimisé (code superflu) | Optimisée (CodeBloatOptimized) |
|---|---|---|
base.odex taille madvise |
~7,9 Mo (2,0 ms) | ~16 Ko (0,003 ms) |
base.apk taille madvise |
~2,4 Mo (2,4 ms) | ~4 Ko (0,001 ms) |
classes2.dex taille madvise |
~7,3 Mo (8,6 ms) | N/A |
classes3.dex taille madvise |
~7,3 Mo (8,0 ms) | N/A |
Durée totale de madvising |
~21 ms | ~ 0,004 ms |
Performances de chargement des applications non optimisées

Performances de chargement des applications optimisées

L'impact de l'inflation du code varie en fonction de la taille de l'application, des caractéristiques de l'appareil de l'utilisateur et de la charge du système.
PerfettoSQL pour l'analyse du chargement
Vous pouvez utiliser les requêtes suivantes pour extraire ces métriques de vos traces.
1. Durée de démarrage de l'application
Il s'agit du temps écoulé entre le lancement de l'activité d'une application et le moment où l'activité a dessiné un premier frame.
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT package, dur, startup_type
FROM android_startups
WHERE package LIKE 'com.android.codebloat%';
Consultez : Comprendre les différents états de démarrage de l'application
La durée de démarrage d'une application est sensible à de nombreux facteurs autres que ceux abordés dans ce guide.
2. Extraire les tailles et les durées madvising
Cette requête se concentre sur la partie madvising que nous avons vue ci-dessus.
INCLUDE PERFETTO MODULE slices.with_context;
SELECT
name,
dur/1e6 AS dur_ms
FROM thread_slice
WHERE process_name LIKE 'com.android.codebloat%'
AND name LIKE 'madvising %';
3. Répartition de l'état du thread principal (durée totale par état)
Cette requête indique le temps passé par le thread principal de l'application dans différents états.
SELECT
p.name AS process_name,
state,
sum(dur)/1e6 AS total_dur_ms
FROM thread_state ts
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.codebloat%'
AND t.is_main_thread = 1
GROUP BY p.name, state;
Vous pouvez affiner la requête pour n'examiner que les états du thread principal pendant la durée de démarrage de l'application.
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT
p.name AS process_name,
ts.state,
-- Calculate only the duration that falls within the startup window
SUM(
MAX(0,
MIN(ts.ts + ts.dur, s.ts + s.dur) - MAX(ts.ts, s.ts)
)
) / 1e6 AS startup_dur_ms
FROM thread_state ts
JOIN thread t USING (utid)
JOIN process p USING (upid)
-- Join on the package name to align thread states with the correct startup
JOIN android_startups s ON s.package = p.name
WHERE p.name LIKE 'com.android.codebloat%'
AND t.is_main_thread = 1
-- Only select thread states that overlap with the startup interval
AND ts.ts + ts.dur > s.ts
AND ts.ts < s.ts + s.dur
GROUP BY 1, 2
ORDER BY startup_dur_ms DESC;
Cela peut révéler des problèmes intéressants, par exemple :
- Temps élevé passé à l'état "Exécutable" (R), mais pas à l'état "En cours d'exécution" : cela indique que le démarrage de l'application a été retardé par une contention du processeur, c'est-à-dire que le thread principal de l'application n'a pas pu s'exécuter, car d'autres threads (éventuellement d'autres applications) occupaient les processeurs.
- Temps passé élevé en veille interruptible (D) : cela indique généralement une lenteur des E/S ou une pression sur la mémoire qui bloque le démarrage de l'application.
- Temps de veille (S) élevé : cela signifie que le thread principal attendait que d'autres threads effectuent des tâches. Parfois, cela indique une contention de verrouillage dans le chemin de démarrage de l'application (c'est-à-dire que le thread principal était bloqué sur une ressource exclusive occupée par un autre thread de l'application).
4. Mémoire maximale soutenue par des fichiers (fichier RSS)
Cette métrique est fortement corrélée à la quantité de code et de données que l'application charge au démarrage. Une application plus "gonflée" atteindra un nombre plus élevé ici, ce qui entraînera une pression sur la mémoire du système. Cette pression peut à son tour retarder le démarrage de l'application, car le système a du mal à satisfaire les demandes d'allocation ou détourne le temps CPU de la tâche de démarrage de l'application pour le consacrer à la récupération de mémoire auprès d'autres processus afin de répondre aux besoins immédiats de l'application qui démarre.
SELECT
p.name AS process_name,
max(c.value)/1024.0/1024.0 AS max_rss_file_mb
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.codebloat%'
AND t.name = 'mem.rss.file'
GROUP BY p.name;
Modes de compilation et mémoire ART
Android Runtime (ART) peut compiler le code de votre application dans l'un des différents modes, également appelés filtres de compilation. Le filtre de compilateur sélectionné a un impact direct sur l'espace mémoire utilisé de votre application.
verify: ART n'effectue que la validation du bytecode. Aucune compilation AOT n'est effectuée. Le code est exécuté via l'interpréteur ou compilé au moment de l'exécution par le compilateur JIT.- Impact sur la mémoire : taille sur le disque la plus petite. L'utilisation de la mémoire par le code natif est transférée dans
JIT Cache(mémoire sale anonyme).
- Impact sur la mémoire : taille sur le disque la plus petite. L'utilisation de la mémoire par le code natif est transférée dans
speed: ART effectue une compilation AOT complète de toutes les méthodes.- Impact sur la mémoire : taille
.odexla plus élevée. Maximise l'utilisation de la mémoire propre soutenue par des fichiers.
- Impact sur la mémoire : taille
speed-profile: ART ne compile que les méthodes qui ont été marquées comme "chaudes" dans un profil JIT.- Impact sur la mémoire : approche équilibrée. Seul le code le plus critique est compilé AOT.
Le filtre le plus courant est speed-profile, qui est utilisé lors de l'installation d'applications utilisateur. Ce paramètre est configuré dans les propriétés système pm.dexopt.install et pm.dexopt.bg-dexopt, et est généralement défini dans build/make/target/product/runtime_libart.mk.
Certaines applications système utiliseront la compilation speed et seront également compilées lors de la création de l'image système. verify n'est généralement utilisé que dans les cas d'utilisation de développement.
| Cas d'utilisation | Filtre de compilateur typique |
|---|---|
| Développement | verify |
| Image système | speed |
| Applications utilisateur | speed-profile |
Exercice pratique : modes de compilation et mémoire
Nous pouvons utiliser l'application CodeBloat pour voir comment ces filtres affectent la mémoire. Pour reproduire ces mesures :
- Forcez la recompilation de l'application en mode cible.
- Forcez l'arrêt de l'application et redémarrez-la à froid.
- Attendez que le thread d'arrière-plan ait fini de toucher les classes (consultez logcat ou attendez 5 secondes).
- Exécutez
adb shell dumpsys meminfo com.android.codebloat.
Mode : verify (sans AOT)
adb shell cmd package compile -m verify -f com.android.codebloat
adb shell am force-stop com.android.codebloat
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5
adb shell dumpsys meminfo com.android.codebloat
En mode verify, le récapitulatif de l'application affiche les éléments suivants : * PSS du code : environ 8 000 Ko * Dalvik
Autre (JIT) : environ 25 000 Ko
Comme aucun code n'est compilé AOT, l'environnement d'exécution doit compiler les méthodes à chaud en JIT dans le cache JIT, qui apparaît sous la forme de mémoire anonyme modifiée (Dalvik
Other).
Mode : speed (AOT complet)
adb shell cmd package compile -m speed -f com.android.codebloat
adb shell am force-stop com.android.codebloat
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5
adb shell dumpsys meminfo com.android.codebloat
En mode speed, les résultats changent radicalement : * Code PSS : ~24 000 ko * Autre Dalvik (JIT) : ~5 000 ko
Le code de l'application est désormais mappé à partir du fichier .odex en tant que mémoire propre sauvegardée dans un fichier. Cela réduit la pression sur le cache JIT et rend la mémoire éligible à l'éviction en cas de pression, au lieu d'être "bloquée" en tant que RAM modifiée.
Mode : speed-profile (AOT sélectif)
Les applications modernes peuvent regrouper un profil de référence baseline.prof. ART utilise cette fonctionnalité pour compiler de manière sélective uniquement le code nécessaire à un démarrage rapide et efficace en termes de mémoire.
Dans cet exercice, nous allons créer un profil de référence pour lister les classes de démarrage de l'application. Toutefois, en réalité, le compilateur peut également recevoir des profils provenant de sources externes, telles que le Play Store ("profils cloud"), qui peuvent fournir des profils JIT crowdsourcés pour les applications, que le développeur ait également regroupé un profil de référence qu'il a généré ou non.
Générer et utiliser des profils sur l'appareil
Pour voir l'impact de speed-profile, vous pouvez générer votre propre profil sur l'appareil :
Réinitialiser et démarrer :
adb shell am force-stop com.android.codebloatInteragir : démarrez l'application et laissez-la exécuter sa séquence de démarrage.
Vider le profil :
adb shell kill -s SIGUSR1 $(pidof com.android.codebloat)(Cela force l'application à écrire son profil actuel sur le disque.)
Installer le profil :
adb shell cp /data/misc/profiles/cur/0/com.android.codebloat/primary.prof \ /data/misc/profiles/ref/com.android.codebloat/primary.profCompiler :
adb shell cmd package compile -m speed-profile -f com.android.codebloat
Lorsque vous relancerez l'application, vous verrez un solde : Code PSS sera inférieur à speed (par exemple, environ 16 000 Ko), car seules les méthodes de démarrage "à chaud" ont été compilées, le reste étant géré par l'interpréteur ou le JIT uniquement s'ils sont réellement utilisés.
Consultez les pages suivantes :
- Présentation des profils de référence
- Différence entre les profils de référence et les profils de démarrage
Présentation détaillée du code compilé
Si vous souhaitez voir exactement les instructions générées par ART, consultez art/DISASSEMBLY_GUIDE.md.
Il fournit des instructions détaillées sur l'utilisation :
oatdump: pour afficher les instructions ARM64 dans un fichier.odexexistant.dex2oat: simule la compilation avec des indicateurs de débogage détaillés.
Exercice : intégration de code
L'une des raisons pour lesquelles le code compilé peut croître de manière inattendue est l'intégration de méthodes. Le compilateur peut décider de copier le corps d'une petite méthode fréquemment appelée directement dans ses appelants.
Dans notre application CodeBloat, la méthode doSomething() de chaque classe générée appelle simplement method0(). Lorsqu'il est compilé en mode speed, le compilateur d'optimisation d'ART est susceptible d'intégrer method0() à doSomething().
Exercice : Vérifiez cela à l'aide de oatdump sur votre appareil :
# 1. Find the path to the application's APK and compiled .odex file
adb shell pm path com.android.codebloat
# Output: package:/data/app/~~.../base.apk
adb shell "dumpsys package com.android.codebloat | grep 'location is' | head -n 1"
# Example output: [location is /data/app/~~.../oat/arm64/base.odex]
# 2. Run oatdump (substituting the correct path to base.odex)
adb shell oatdump --oat-file=/data/app/~~.../oat/arm64/base.odex \
--class-filter=com.android.codebloat.GeneratedClass0
Recherchez la méthode doSomething dans le résultat. Si elle a été intégrée, vous verrez les instructions permettant de charger la constante de chaîne longue directement dans doSomething, plutôt qu'une instruction bl ciblant method0.
Visualiser l'optimisation (CFG)
Pour savoir exactement quand le compilateur a décidé d'intégrer la méthode, vous pouvez générer un graphique de flux de contrôle (CFG). Cela montre l'état du code à chaque étape du pipeline d'optimisation, avec chaque transformation sur la représentation intermédiaire (IR) du compilateur jusqu'à ce que le code soit abaissé à l'ISA cible (par exemple, ARM64).
Exécutez
dex2oatavec des indicateurs de vidage : utilisez l'indicateur--verbose-methodspour limiter la sortie à des méthodes spécifiques. Sinon, le fichier.cfgd'une grande application peut atteindre plusieurs gigaoctets.# Substitution of actual paths required: adb shell dex2oat64 --dex-file=/data/app/~~.../base.apk \ --oat-file=/data/local/tmp/dump.odex \ --compiler-filter=speed \ --dump-cfg=/data/local/tmp/codebloat.cfg \ --verbose-methods=doSomethingExtraire et afficher : extrayez le fichier
.cfgvers votre poste de travail et ouvrez-le avec IR Hydra.Recherchez l'Inliner : dans IR Hydra, chargez les artefacts de compilation et recherchez
doSomething. Comparez la représentation avant et après la passe Inliner. Le graphique se développe à mesure que les instructions demethod0sont fusionnées dans l'appelant.
Vous pouvez également utiliser l'outil Opt Pipeline dans Compiler Explorer (comme décrit dans la section ci-dessus) et saisir un code similaire pour voir une transformation similaire effectuée lors de la transmission Inliner.
Exercice : champs volatils et barrières mémoire
Dans l'application MemoryLab, le champ mGarbageSink est marqué comme volatile. Cela garantit que le compilateur n'optimise pas nos allocations de déchets.
public volatile byte[] mGarbageSink;
Dans le désassemblage ARM64, vous verrez que chaque magasin de ce champ est accompagné d'une barrière mémoire (dmb ish) ou utilise des instructions Load-Acquire/Store-Release (ldar/stlr). Cela garantit la visibilité des threads, mais ajoute quelques instructions supplémentaires à chaque accès, ce qui augmente légèrement la taille du code par rapport à un champ normal.
Exercice : Trouvez les accès aux champs et les barrières de mémoire associées dans le désassemblage.
Exercice : vérifications de la suspension implicite
Si vous désassemblez une boucle, comme celle de generateAllocationChurn, vous remarquerez une instruction curieuse à la fin du corps de la boucle :
ldr x21, [x21]
Il s'agit d'une vérification de la suspension implicite. ART l'utilise pour permettre au collecteur de déchets de mettre en pause les threads de manière sécurisée. Le registre x21 pointe normalement vers lui-même.
Lorsque le GC doit suspendre le thread, il "empoisonne" cet emplacement de mémoire. La prochaine fois que le thread exécutera ce ldr, il déclenchera une erreur que le runtime interceptera et utilisera pour faire passer le thread à l'état suspendu.
Ce modèle est répété dans chaque boucle et au début de chaque méthode, ce qui contribue à la taille totale du code de votre application.
Exercice : Trouvez toutes les vérifications de suspension implicites dans le désassemblage de la méthode et essayez de les corréler au code source d'origine.
← WebView | ↑ Haut | Threads →