← Tous les articles

Les nouveautés d'Instruments 27 pour la réactivité des apps

Lors de la WWDC 2026, deux ingénieurs d’Apple ont profilé une app de prise de notes qui présentait trois blocages distincts : un crayon qui ne répondait plus lors de l’enregistrement, un défilement saccadé et un gel à l’usage de l’outil lasso. Ils ont corrigé les trois en associant un nouvel outil d’Instruments 27 à chaque symptôme, puis ont prouvé chaque correctif par une comparaison entre version de référence et version optimisée.1 La thèse de la session est un flux de diagnostic : lisez le CPU pendant le blocage, et le CPU vous indique quel instrument ouvrir ensuite. Un CPU élevé signifie que votre code est trop lent. Un CPU inactif signifie que votre code est bloqué. Instruments 27 fournit l’outillage pour résoudre les deux, ainsi qu’une nouvelle vue pour confirmer que le correctif a vraiment fonctionné.

Cet article parcourt les trois outils qui ancrent ce flux : Top Functions pour repérer le poids propre le plus lourd quand le CPU est saturé, le nouvel instrument Swift executors pour voir sur quel executor une tâche s’est exécutée quand le travail se dispute le Main Actor, et le nouveau panneau Inspector pour lire les arguments d’un appel système quand un thread reste inactif en attente du système. Run Comparisons relie le tout en mesurant si chaque changement a amélioré la trace. Tout ce qui suit provient directement de la session.

TL;DR

  • Instruments 27 réorganise le workflow de réactivité autour d’une règle de diagnostic : ouvrez d’abord le Time Profiler, vérifiez le CPU du thread principal pendant le blocage, et laissez cette lecture vous orienter vers le bon outil.1
  • Top Functions est un nouveau mode d’analyse qui écarte la hiérarchie des appels et fusionne, selon le poids propre, chaque nœud dispersé, faisant remonter la surcharge d’exécution qu’un flame graph fragmente entre les branches.1
  • Run Comparisons est « New in Instruments » et calcule l’écart de performance exact entre une trace de référence et une trace optimisée, en faisant correspondre chaque fonction d’une exécution à l’autre et en colorant les régressions en rouge et les améliorations en vert.1
  • Le nouvel instrument Swift executors visualise le Main Actor, l’executor concurrent global et tout executor personnalisé, ce qui vous permet de voir sur quel executor une tâche s’est exécutée et de repérer une contention du Main Actor.1
  • Le nouveau panneau Inspector fait apparaître les arguments exacts d’un appel système (descripteur de fichier, adresse du tampon, taille d’écriture) et sépare le temps on-core du temps off-core, exposant un blocage synchrone comme une écriture de 1,7 Go sur le thread principal.1

Le flux de diagnostic autour duquel Instruments 27 est construit

Avant tout nouvel outil, la session établit la règle qui les organise. Quand une app perd des images ou se bloque, la première étape est le Time Profiler, qui donne la vue d’ensemble de haut niveau pour vous orienter.1 À partir de là, une seule question aiguille tout : que fait le CPU pendant le blocage ?

Si l’utilisation du CPU est élevée, le thread est occupé et le travail prend trop de temps, ce qui pointe vers un goulet d’étranglement de performance du code. Vous corrigez cela de deux façons : refactoriser l’algorithme pour qu’il s’exécute plus vite, ou, lorsque la charge lourde est inévitable, la déporter vers une tâche d’arrière-plan pour que l’interface reste réactive.1 Si l’app se bloque alors que le processeur reste inactif, optimiser les algorithmes n’aidera pas, car le thread principal est coincé à attendre qu’une ressource se libère : E/S de fichier, un verrou de synchronisation ou une communication interprocessus. Comme le dit la session : « Comme le Time Profiler ne surveille que les cycles CPU actifs, il n’offre aucune visibilité sur ces événements. »1

Les ingénieurs ont profilé une version release de l’app de notes, car « une version debug sacrifie la performance d’exécution au profit de la facilité de débogage, si bien que les données de profilage des versions debug peuvent être trompeuses. »1 Ils ont choisi le modèle Swift Concurrency, qui expose tout de même l’instrument Time Profiler, et ont enregistré les trois blocages dans une seule trace de référence. Ils ont aussi enveloppé la sélection au lasso dans un intervalle os_signpost à l’aide du type OSSignposter, en réglant la catégorie sur points of interest pour qu’Instruments fasse apparaître l’intervalle dans la piste points of interest. Ce signpost devient le point d’ancrage pour filtrer la trace et, plus tard, pour une comparaison d’exécutions sans bruit.1

Watch on Apple Developer ↗

Art et Harjas exposent le flux de diagnostic avant la démo, à partir de 1:50.

La fenêtre d’Instruments 27 elle-même structure le workflow. La timeline en haut affiche des pistes horizontales pour les tâches, les acteurs et les executors. La zone de détail en dessous change selon la piste sélectionnée. À droite se trouve « un tout nouveau panneau Inspector » qui fait apparaître des détails et des actions supplémentaires en fonction de votre sélection.1 Trois outils remplissent ce cadre, chacun lié à l’un des trois blocages.

Top Functions : quand le CPU est saturé

Le blocage du lasso se lit comme un CPU élevé. Après avoir filtré la trace sur l’intervalle os_signpost du lasso, l’instrument hangs y a confirmé plusieurs blocages, et le déploiement de la piste de processus jusqu’au thread principal a montré le CPU « restant autour de 100 % pendant cette période. »1 Un CPU élevé signifie que le code s’exécute mais prend trop de temps, donc le Time Profiler est le bon outil.

La session explique pourquoi un flame graph seul ne suffit pas ici. Le Time Profiler utilise une minuterie matérielle pour échantillonner la pile d’appels à une cadence par défaut d’une milliseconde, enregistrant la pile courante sur chaque cœur. Chaque fonction échantillonnée reçoit un poids, et la fonction au bas de la pile reçoit un poids propre, le temps passé à exécuter des instructions directement à l’intérieur d’elle.1 Un flame graph rend cet arbre d’appels en blocs spatiaux, avec les appelants en haut, les appelés s’étendant vers le bas, et la largeur des barres proportionnelle au temps CPU total.1 Le problème : « pour le code appelé depuis de nombreux endroits, comme les fonctions du runtime Swift et divers utilitaires d’aide », le flame graph répartit le coût total sur chaque branche qui les appelle. Le temps d’exécution se fragmente en petits morceaux, ce qui « rend difficile de répondre à la question : quelles fonctions précises ont brûlé le plus de cycles au total ? »1

C’est cet écart que Top Functions comble. Comme la session décrit le nouveau mode : « Ce nouveau mode écarte la hiérarchie des appels. Au lieu de cela, il extrait chaque nœud dispersé et les fusionne pour former un seul bloc », évalué selon la métrique self.1 Parcourir le flame graph du lasso n’a révélé aucun coupable unique, simplement « différents chemins de code qui, additionnés, deviennent assez coûteux pour provoquer des blocages. »1 En passant à Top Functions, trié par poids propre, l’entrée en tête était swift_project_boxed_opaque_existential, la fonction du runtime qui déballe un existentiel pour que le code puisse opérer dessus.1

Le correctif résidait dans le système de types, pas dans la timeline. L’ingénieur a demandé à l’assistant de code de Xcode de réécrire le code de dessin pour utiliser des types concrets et des génériques plutôt que des existentiels, puisque les existentiels peuvent varier en taille et exigent un travail supplémentaire pour y accéder, ce qui s’est avéré trop coûteux pour ce cas d’usage.1 La leçon pour l’outil : Top Functions existe pour attraper la surcharge logicielle dispersée qu’aucune branche unique d’un flame graph ne rend évidente.

Run Comparisons : prouver que le correctif a fonctionné

Confirmer un correctif signifiait autrefois ouvrir deux traces dans des fenêtres séparées et comparer à l’œil les données de Top Functions côte à côte. Instruments 27 remplace cela par Run Comparisons, décrit dans la session comme « New in Instruments. »1 Il « calcule l’écart de performance exact en croisant tous les échantillons de la trace de référence et de la trace optimisée », en évaluant chaque nœud de la pile. Il fait correspondre l’ancienne version d’une fonction de l’exécution de référence à la nouvelle version de l’exécution optimisée, calcule l’écart et trie par différence de performance. Un bloc rouge marque une régression ; un bloc vert marque une amélioration.1

Le workflow met un soin délibéré à éliminer le bruit. Les ingénieurs ont d’abord filtré les deux exécutions sur exactement le même intervalle os_signpost pour la sélection au lasso, puis ont sélectionné la piste du thread principal et cliqué sur le bouton de comparaison pour choisir l’exécution de référence dans un menu déroulant.1 Cela ajoute un onglet de comparaison à la barre latérale, et « vous pouvez créer plusieurs comparaisons, et elles sont enregistrées dans le document pour faciliter la collaboration. »1

La comparaison a raconté une histoire honnête. L’arbre d’appels textuel a montré que le temps d’exécution global du lasso avait diminué. Le flame graph a montré les chemins améliorés en vert et les chemins régressés en rouge, et les régressions étaient « de nouvelles fonctions ajoutées par l’assistant de code à mesure qu’il travaillait à éliminer l’usage des existentiels. »1 Dans la vue Top Functions, les régressions se trient par défaut en tête ; inverser le tri a révélé que swift_project_boxed_opaque_existential « a été complètement supprimé », et que « globalement, les améliorations l’emportent (out[weigh]) sur les régressions. »1 Cet écart est l’étape de vérification : Run Comparisons existe pour qu’un correctif soit un résultat mesuré, et non un résultat espéré. Le conseil constant de la session est de « tirer parti d’os_signpost pour vous assurer que vos intervalles pour les comparaisons d’exécutions sont fiables. »1

L’instrument Swift executors : quand les tâches se disputent le Main Actor

Le blocage du défilement n’avait aucun journal de points of interest sur lequel s’appuyer. La session se tourne vers un autre type de contexte : « quelles tâches sont sur le Main Actor pendant ces blocages. »1 C’est le rôle du nouvel instrument Swift executors, qui « visualise le Main Actor, l’executor concurrent global et tout executor personnalisé de votre processus. »1

Pour chaque blocage du défilement, la piste du Main Actor montrait une tâche Swift nommée renderThumbnail. Sélectionner la piste résumait les tâches sur le Main Actor et révélait « plusieurs tâches render thumbnail sur le Main Actor prenant quelques centaines de ms à s’exécuter », ce qui correspond au défilement saccadé.1 En suivant le flux de diagnostic, l’ingénieur a filtré sur un seul blocage et utilisé l’Inspector pour épingler le thread principal ; le Time Profiler a indiqué un CPU « autour de 100 % », écartant une attente de ressource système. Les tâches prenaient simplement trop de temps sur le Main Actor.1

La cause racine est une subtilité d’héritage de contexte que l’instrument rend visible. Le Main Actor gère toutes les mises à jour et interactions de l’interface. L’app rendait les vignettes de façon asynchrone, mais comme ce code était appelé depuis SwiftUI, « il héritait du contexte du Main Actor », si bien que les tâches de vignettes entraient en concurrence avec des mises à jour critiques de l’interface.1 Le correctif aiguille le travail vers le pool de threads. L’ingénieur a ajouté l’attribut @concurrent à l’initialiseur de la tâche, ce qui déplace la tâche de rendu hors du Main Actor vers l’executor global, et le compilateur Swift vérifie que le changement n’introduit aucune situation de compétition.1 L’instrument a confirmé le déplacement : dans la trace mise à jour, « les tâches de rendu des vignettes sont passées de la piste du Main Actor à la piste de l’executor global », et le travail s’exécute désormais en parallèle.1 Le point clé pour l’outil : l’instrument Swift executors existe pour identifier la congestion d’acteur en vous montrant exactement quel executor a exécuté quelle tâche.

Le panneau Inspector : quand le thread est inactif et bloqué

Le blocage de l’enregistrement inverse les deux premiers. Après avoir filtré sur l’intervalle os_signpost Write to File et zoomé, le micro-blocage signalé montrait un CPU « oscillant autour de 20 %. »1 Un CPU bas est trompeur : « cela ne veut pas dire que votre code s’exécute lentement, cela veut dire que le thread a cessé de tourner. »1 Quand l’interface gèle sous un CPU bas, le thread principal est bloqué en attente d’une ressource système, et optimiser les algorithmes n’apporte rien « parce qu’il n’y a aucun code en cours d’exécution à optimiser. »1

Pour ce symptôme, la session bascule vers le modèle System Trace, « conçu pour visualiser exactement quand et pourquoi le système d’exploitation met votre application en pause. »1 La session parcourt le modèle des états de thread : un thread en cours d’exécution qui rencontre une ressource indisponible entre dans l’état bloqué, le noyau l’évince du processeur, et ce n’est que lorsque la ressource est prête qu’il devient exécutable (runnable) et attend que l’ordonnanceur lui assigne un cœur libre. Ces brefs réveils pour coordonner l’étape suivante de la requête sont exactement « ce qui provoque ces vingt pour cent d’utilisation du CPU. »1

Dans le System Trace, la voie d’activité de l’enregistrement montrait « une grande quantité d’espace vide », indiquant que le thread était bloqué, avec des intervalles violets marquant un appel système en cours d’exécution.1 Sélectionner un intervalle a mis en évidence plus que le segment cliqué, visualisant « un seul appel système d’écriture continu qui s’étend à la fois sur le temps on-core et off-core » : les segments opaques sont l’exécution on-core, les segments translucides sont le blocage off-core.1

Le nouveau panneau Inspector transforme cela en verdict. Comme l’indique la session : « L’Inspector nous donne les arguments exacts passés à cet appel système. Nous pouvons voir le descripteur de fichier cible, l’adresse mémoire du tampon et, surtout, la taille. »1 La taille était la preuve accablante : l’app « tentait d’écrire plus de 1,7 gigaoctet de données sur le thread principal. »1 L’Inspector a aussi montré le coût : la seule opération « a pris plus de 500 millisecondes, et près de 300 de ces millisecondes ont été passées off-core à attendre le disque. »1 L’appel synchrone data.write était le goulet d’étranglement. Envelopper l’encodage et l’écriture dans une tâche Swift l’a poussé vers le pool de threads concurrent, et une trace de vérification a confirmé que l’appel système d’écriture apparaît désormais sur un thread d’arrière-plan, et non sur le thread principal.1 Le panneau Inspector existe pour exposer le comportement de blocage synchrone, comme les E/S de fichier, qu’un profileur centré sur le seul CPU ne peut pas voir.

Points clés à retenir

Pour les ingénieurs iOS et macOS :

  • Ouvrez d’abord le Time Profiler et lisez le CPU du thread principal pendant le blocage ; un CPU élevé vous envoie vers Top Functions, un CPU inactif vous envoie vers System Trace et l’Inspector.1
  • Utilisez Top Functions quand un flame graph ne montre aucun coupable unique ; il fusionne la surcharge d’exécution dispersée par poids propre pour que la fonction la plus coûteuse remonte.1

Pour les équipes qui livrent des correctifs de performance :

  • Vérifiez chaque changement avec Run Comparisons, filtré sur le même intervalle os_signpost, et lisez l’écart rouge/vert au lieu de vous fier à une comparaison à l’œil côte à côte.1
  • Les comparaisons s’enregistrent dans le document et empilent plusieurs exécutions, de sorte que la preuve d’un correctif accompagne la trace lors de la revue.1

Pour le travail sur la concurrence et la réactivité :

  • Tournez-vous vers l’instrument Swift executors quand le travail se dispute le Main Actor ; il montre si une tâche s’est exécutée sur le Main Actor, l’executor concurrent global ou un executor personnalisé.1
  • Profilez toujours une version release, puisque les versions debug produisent des données trompeuses.1

FAQ

Qu’est-ce que le mode Top Functions dans Instruments 27 ?

Top Functions est un nouveau mode d’analyse du Time Profiler. Il écarte la hiérarchie des appels et fusionne chaque instance dispersée d’une fonction en un seul bloc, classé par poids propre, le temps passé à exécuter des instructions directement à l’intérieur de cette fonction. Il répond à une question avec laquelle un flame graph se débat : quelles fonctions précises ont brûlé le plus de cycles au total quand leur coût est fragmenté entre de nombreuses branches appelantes, comme les fonctions du runtime Swift et les utilitaires d’aide.1

Comment fonctionnent les Run Comparisons dans Instruments ?

Run Comparisons, décrit dans la session comme nouveau dans Instruments, calcule l’écart de performance exact entre une trace de référence et une trace optimisée. Il fait correspondre chaque fonction entre les deux exécutions, calcule l’écart pour chaque nœud de la pile et trie par différence de performance, en colorant les régressions en rouge et les améliorations en vert. Pour une comparaison propre, vous filtrez les deux exécutions sur le même intervalle os_signpost, sélectionnez une piste et choisissez la référence dans un menu déroulant ; les comparaisons s’enregistrent dans le document.1

Que montre l’instrument Swift executors ?

L’instrument Swift executors d’Instruments 27 visualise le Main Actor, l’executor concurrent global et tout executor personnalisé de votre processus. Il vous permet de voir sur quel executor une tâche Swift donnée s’est exécutée, afin de repérer une contention du Main Actor. Dans la session, il a révélé des tâches renderThumbnail coincées sur le Main Actor parce que du code appelé depuis SwiftUI héritait du contexte du Main Actor ; les déplacer vers l’executor global a levé le blocage.1

Comment trouver un blocage causé par des E/S de fichier ?

Quand l’interface gèle mais que le CPU du thread principal est bas (autour de 20 pour cent dans la session), le thread est bloqué en attente d’une ressource système plutôt qu’en train d’exécuter du code lent. Basculez vers le modèle System Trace et sélectionnez l’intervalle d’appel système ; le nouveau panneau Inspector montre les arguments exacts du syscall, dont le descripteur de fichier, l’adresse du tampon et la taille d’écriture, ainsi que le temps on-core par rapport au temps off-core. Dans la démo, il a exposé une écriture synchrone de 1,7 Go sur le thread principal.1


Le flux de diagnostic enseigné par cette session se conjugue avec le versant SwiftUI de la réactivité dans Performance et interopérabilité de SwiftUI sur iOS 27 et avec l’état d’esprit de mesure dans l’angle mort de la performance. Le mouvement de concurrence qui a levé le blocage du Main Actor s’inscrit dans le modèle plus large couvert par La concurrence de Swift 6.2 en pratique. Le hub complet de la série est la Apple Ecosystem Series.

Références


  1. Apple, WWDC 2026 session 268, Profile, fix, and verify: Improve app responsiveness with Instruments. Source du flux de diagnostic (Time Profiler d’abord, la lecture du CPU du thread principal aiguille l’enquête), des recommandations sur la version release et le modèle Swift Concurrency, de l’intervalle points of interest os_signpost / OSSignposter, du nouveau panneau Inspector, du modèle d’échantillonnage de l’arbre d’appels et du flame graph (cadence par défaut d’une milliseconde, poids propre), du mode d’analyse Top Functions et de la découverte de swift_project_boxed_opaque_existential, de Run Comparisons (« New in Instruments ») avec les écarts rouge/vert et les onglets de comparaison enregistrés dans le document, de l’instrument Swift executors (Main Actor, executor concurrent global, executors personnalisés) et de la contention du Main Actor de renderThumbnail résolue avec l’attribut @concurrent, ainsi que du diagnostic System Trace plus Inspector d’une écriture synchrone de 1,7 Go (descripteur de fichier, adresse du tampon, taille d’écriture, temps on-core par rapport au temps off-core, plus de 500 ms dont près de 300 ms off-core). 

Articles connexes

Ce que l'équipe Performance d'Apple a dit lors du lab de la WWDC26

L'équipe Power & Performance d'Apple a répondu en direct aux questions des développeurs à la WWDC26. Conseils de terrain…

15 min de lecture

MetricKit reconstruit : télémétrie sensible à l'état dans iOS 27

MetricKit est reconstruit dans iOS 27 : flux asynchrones de métriques et de diagnostics, rapports Codable et un framewor…

13 min de lecture

De 76 à 100 : obtenir un score Lighthouse parfait

Comment un site portfolio personnel est passé d'un score de performance mobile Lighthouse de 76 avec un CLS de 0,493 à u…

7 min de lecture