← Alle Beitrage

Was Instruments 27 für die App-Reaktionsfähigkeit Neues bietet

Auf der WWDC 2026 analysierten zwei Apple-Ingenieure eine Notizen-App, die drei voneinander getrennte Hänger zeigte: einen nicht reagierenden Stift beim Speichern, ruckeliges Scrollen und ein Stocken beim Einsatz des Lasso-Werkzeugs. Sie behoben alle drei, indem sie jedem Symptom ein neues Werkzeug aus Instruments 27 zuordneten, und belegten anschließend jede Korrektur mit einem Vergleich von Baseline und optimierter Version.1 Die These der Session ist ein Diagnoseablauf: Lesen Sie die CPU während des Hängers, und die CPU verrät Ihnen, welches Instrument Sie als Nächstes öffnen sollten. Hohe CPU-Last bedeutet, dass Ihr Code zu langsam ist. Eine untätige CPU bedeutet, dass Ihr Code blockiert ist. Instruments 27 liefert das Werkzeug, um beides zu lösen, sowie eine neue Ansicht, mit der sich bestätigen lässt, dass die Korrektur tatsächlich gewirkt hat.

Dieser Beitrag geht die drei Werkzeuge durch, die diesen Ablauf tragen: Top Functions, um bei gesättigter CPU das schwere Self-Weight zu finden, das neue Swift executors-Instrument, um zu sehen, auf welchem Executor eine Task lief, wenn Arbeit um den Main Actor konkurriert, und das neue Inspector-Panel, um Syscall-Argumente zu lesen, wenn ein Thread untätig auf das System wartet. Run Comparisons verbindet alles, indem es misst, ob jede Änderung den Trace verbessert hat. Alles Folgende stammt direkt aus der Session.

TL;DR

  • Instruments 27 ordnet den Workflow zur Reaktionsfähigkeit um eine Diagnoseregel herum neu: Öffnen Sie zuerst den Time Profiler, prüfen Sie während des Hängers die CPU-Last des Main-Threads, und lassen Sie sich von diesem Messwert zum richtigen Werkzeug leiten.1
  • Top Functions ist ein neuer Analysemodus, der die Aufrufhierarchie verwirft und jeden verstreuten Knoten nach Self-Weight zusammenführt, sodass der Laufzeit-Overhead sichtbar wird, den ein Flame Graph über die Zweige hinweg zersplittert.1
  • Run Comparisons ist „New in Instruments” und berechnet die exakte Leistungsdifferenz zwischen einem Baseline-Trace und einem optimierten Trace, indem es jede Funktion über die Läufe hinweg zuordnet und Regressionen rot, Verbesserungen grün einfärbt.1
  • Das neue Swift executors-Instrument visualisiert den Main Actor, den globalen Concurrent Executor und beliebige benutzerdefinierte Executors, sodass Sie sehen, auf welchem Executor eine Task lief, und Main-Actor-Konkurrenz erkennen.1
  • Das neue Inspector-Panel macht die exakten Argumente eines Systemaufrufs sichtbar (File-Descriptor, Pufferadresse, Schreibgröße) und trennt On-Core- von Off-Core-Zeit, wodurch synchrones Blockieren wie ein 1,7-GB-Schreibvorgang auf dem Main-Thread offengelegt wird.1

Der Diagnoseablauf, um den Instruments 27 herum aufgebaut ist

Noch vor jedem neuen Werkzeug stellt die Session die Regel auf, die sie alle ordnet. Wenn eine App Frames verliert oder hängt, ist der erste Schritt der Time Profiler, der Ihnen den Überblick auf hoher Ebene verschafft, um sich zu orientieren.1 Von dort aus lenkt eine einzige Frage alles Weitere: Was tut die CPU während des Hängers?

Ist die CPU-Auslastung hoch, ist der Thread beschäftigt und die Arbeit dauert zu lange, was auf einen Engpass in der Code-Performance hindeutet. Das beheben Sie auf zwei Wegen: Sie überarbeiten den Algorithmus, damit er schneller läuft, oder Sie lagern die schwere Last, wenn sie unvermeidbar ist, in eine Background-Task aus, damit die Oberfläche reaktionsfähig bleibt.1 Hängt die App, während der Prozessor untätig ist, hilft das Optimieren von Algorithmen nicht, denn der Main-Thread steckt fest und wartet darauf, dass eine Ressource frei wird: Datei-I/O, eine Synchronisationssperre oder Interprozesskommunikation. Wie es in der Session heißt: „Because Time Profiler only monitors active CPU cycles, it provides no visibility into these events.”1

Die Ingenieure analysierten einen Release-Build der Notizen-App, denn „a debug build trades off runtime performance for debug ability, so profiling data from debug builds can be misleading.”1 Sie wählten das Swift-Concurrency-Template, das weiterhin das Time-Profiler-Instrument bereitstellt, und zeichneten alle drei Hänger in einen einzigen Baseline-Trace auf. Außerdem umschlossen sie die Lasso-Auswahl mit einem os_signpost-Intervall über den Typ OSSignposter und setzten die Kategorie auf Points of Interest, damit Instruments das Intervall in der Points-of-Interest-Spur sichtbar macht. Dieser Signpost wird zum Ankerpunkt für das Filtern des Trace und später für einen rauschfreien Run-Vergleich.1

Watch on Apple Developer ↗

Art und Harjas erläutern den Diagnoseablauf vor der Demo, ab Minute 1:50.

Das Instruments-27-Fenster selbst rahmt den Workflow ein. Die Timeline oben zeigt horizontale Spuren für Tasks, Actors und Executors. Der Detailbereich darunter ändert sich je nach ausgewählter Spur. Rechts befindet sich „a brand new Inspector panel”, das je nach Auswahl zusätzliche Details und Aktionen sichtbar macht.1 Drei Werkzeuge füllen diesen Rahmen aus, jedes an einen der drei Hänger gebunden.

Top Functions: wenn die CPU gesättigt ist

Der Lasso-Hänger zeigt sich als hohe CPU-Last. Nachdem der Trace auf das Lasso-os_signpost-Intervall gefiltert war, bestätigte das Hangs-Instrument dort mehrere Hänger, und als die Prozessspur bis zum Main-Thread aufgeklappt wurde, zeigte die CPU, dass sie „staying around a 100% during this time period.”1 Hohe CPU-Last bedeutet, dass der Code ausgeführt wird, aber zu lange braucht, also ist der Time Profiler das richtige Werkzeug.

Die Session erklärt, warum ein Flame Graph allein hier nicht genügt. Der Time Profiler nutzt einen Hardware-Timer, um den Call Stack mit einer Standardrate von einer Millisekunde abzutasten und den aktuellen Stack auf jedem Core aufzuzeichnen. Jede abgetastete Funktion erhält ein Weight, und die Funktion am unteren Ende des Stacks erhält ein Self-Weight, die Zeit, die direkt mit der Ausführung von Instruktionen in ihr verbracht wurde.1 Ein Flame Graph stellt diesen Aufrufbaum in räumlichen Blöcken dar, mit Aufrufern oben, nach unten wachsenden Aufgerufenen und einer Balkenbreite proportional zur gesamten CPU-Zeit.1 Das Problem: „for code that is called from a lot of places, like Swift runtime functions and various helper utilities” verteilt der Flame Graph die Gesamtkosten über jeden Zweig, der sie aufruft. Die Ausführungszeit zersplittert in kleine Stücke, was es „makes it difficult to answer which specific functions burned the most overall cycles.”1

Genau diese Lücke füllt Top Functions. Wie die Session den neuen Modus beschreibt: „This new mode discards the call hierarchy. Instead, it extracts every single scattered node and merges them together to form one block”, bewertet nach der Self-Metrik.1 Beim Durchsehen des Lasso-Flame-Graphs trat kein einzelner Übeltäter hervor, sondern lediglich „different codepaths sum together to be costly enough to cause hangs.”1 Nach dem Wechsel zu Top Functions, sortiert nach Self-Weight, war der oberste Eintrag swift_project_boxed_opaque_existential, die Laufzeitfunktion, die ein Existential entpackt, damit der Code darüber operieren kann.1

Die Korrektur lag im Typsystem, nicht in der Timeline. Der Ingenieur bat den Xcode-Coding-Assistant, den Zeichencode so umzuschreiben, dass er konkrete Typen und Generics statt Existentials verwendet, da Existentials in ihrer Größe variieren können und einen zusätzlichen Zugriffsaufwand erfordern, der sich für diesen Anwendungsfall als zu teuer erwies.1 Die Lehre für das Werkzeug: Top Functions existiert, um verstreuten Software-Overhead aufzuspüren, den kein einzelner Flame-Graph-Zweig offensichtlich macht.

Run Comparisons: nachweisen, dass die Korrektur gewirkt hat

Eine Korrektur zu bestätigen, hieß früher, zwei Traces in getrennten Fenstern zu öffnen und die Top Functions-Daten nebeneinander mit bloßem Auge zu vergleichen. Instruments 27 ersetzt das durch Run Comparisons, in der Session beschrieben als „New in Instruments.”1 Es „computes the exact performance delta by cross-referencing all of samples from the baseline trace and optimized trace” und bewertet jeden Knoten im Stack. Es ordnet die alte Version einer Funktion aus dem Baseline-Lauf der neuen Version im optimierten Lauf zu, berechnet die Differenz und sortiert nach Performance-Unterschied. Ein roter Block markiert eine Regression, ein grüner Block eine Verbesserung.1

Der Workflow ist bewusst darauf ausgelegt, Rauschen zu entfernen. Die Ingenieure filterten zunächst beide Läufe auf exakt dasselbe os_signpost-Intervall für die Lasso-Auswahl, wählten dann die Main-Thread-Spur und klickten auf die Vergleichsschaltfläche, um den Baseline-Lauf aus einem Dropdown auszuwählen.1 Das fügt der Seitenleiste einen Vergleichstab hinzu, und „you can create multiple comparisons, and they are saved to the document to make collaboration easier.”1

Der Vergleich erzählte eine ehrliche Geschichte. Der textuelle Call Tree zeigte, dass die gesamte Lasso-Ausführungszeit gesunken war. Der Flame Graph zeigte verbesserte Pfade in Grün und verschlechterte Pfade in Rot, und die Regressionen waren „new functions added by the coding assistant as it worked to eliminate the usage of existentials.”1 In der Top Functions-Ansicht werden Regressionen standardmäßig nach oben sortiert; das Umkehren der Sortierung machte sichtbar, dass swift_project_boxed_opaque_existential „has been removed completely” und „overall, the improvements out[weigh] the regressions.”1 Diese Differenz ist der Verifizierungsschritt: Run Comparisons existiert, damit eine Korrektur ein gemessenes Ergebnis ist und kein erhofftes. Der ständige Rat der Session lautet, „leverage os_signpost to make sure your intervals for run comparisons are reliable.”1

Das Swift-executors-Instrument: wenn Tasks um den Main Actor kämpfen

Beim Scroll-Hänger gab es kein Points-of-Interest-Log, an dem man sich orientieren konnte. Die Session greift auf eine andere Art von Kontext zurück: „what tasks are on the Main Actor during these hangs.”1 Das ist die Aufgabe des neuen Swift executors-Instruments, das „visualizes the Main Actor, the global concurrent executor, and any custom executors in your process.”1

Bei jedem Scroll-Hänger zeigte die Main-Actor-Spur eine Swift-Task namens renderThumbnail. Das Auswählen der Spur fasste die Tasks auf dem Main Actor zusammen und enthüllte „several render thumbnail tasks on the Main Actor taking a few hundred ms to run”, was zum ruckeligen Scrollen passt.1 Dem Diagnoseablauf folgend filterte der Ingenieur auf einen einzelnen Hänger und nutzte den Inspector, um den Main-Thread anzupinnen; der Time Profiler meldete eine CPU-Last „around a 100%” und schloss damit ein Warten auf eine Systemressource aus. Die Tasks brauchten schlicht zu lange auf dem Main Actor.1

Die Grundursache ist eine Feinheit der Kontextvererbung, die das Instrument sichtbar macht. Der Main Actor behandelt alle UI-Aktualisierungen und Interaktionen. Die App rendert Thumbnails asynchron, aber weil dieser Code aus SwiftUI heraus aufgerufen wurde, „it inherited the Main Actor context”, sodass die Thumbnail-Tasks mit kritischen UI-Aktualisierungen konkurrierten.1 Die Korrektur leitet die Arbeit in den Thread-Pool um. Der Ingenieur fügte dem Task-Initialisierer das @concurrent-Attribut hinzu, das die Rendering-Task vom Main Actor auf den globalen Executor verlagert, und der Swift-Compiler prüft, dass die Änderung keine Race Conditions einführt.1 Das Instrument bestätigte die Verlagerung: im aktualisierten Trace „the thumbnail rendering tasks have moved from the Main Actor track to the global executor track”, und die Arbeit läuft nun parallel.1 Der springende Punkt für das Werkzeug: Das Swift executors-Instrument existiert, um Actor-Stauungen zu erkennen, indem es Ihnen genau zeigt, welcher Executor welche Task ausführte.

Das Inspector-Panel: wenn der Thread untätig und blockiert ist

Der Speicher-Hänger kehrt die ersten beiden um. Nach dem Filtern auf das Write-to-File-os_signpost-Intervall und dem Hineinzoomen zeigte der gemeldete Mikro-Hänger eine CPU-Last „hovering around 20%.”1 Niedrige CPU-Last ist trügerisch: „it doesn’t mean your code is executing slowly, it means the thread has stopped running.”1 Wenn die Oberfläche bei niedriger CPU-Last einfriert, ist der Main-Thread blockiert und wartet auf eine Systemressource, und das Optimieren von Algorithmen bringt nichts, „because there is no code running to optimize.”1

Für dieses Symptom wechselt die Session zum System-Trace-Template, das „built to visualize exactly when and why the operating system pauses your application.”1 Die Session geht das Thread-Zustandsmodell durch: Ein laufender Thread, der auf eine nicht verfügbare Ressource trifft, geht in den blockierten Zustand über, der Kernel verdrängt ihn vom Prozessor, und erst wenn die Ressource bereit ist, wird er lauffähig und wartet darauf, dass der Scheduler ihm einen freien Core zuweist. Diese kurzen Aufwachvorgänge zur Koordination der nächsten Stufe der Anfrage sind genau das, „what cause that twenty percent of CPU utilization.”1

Im System Trace zeigte die Aktivitätsspur für das Speichern „a large amount of blank space”, was darauf hinweist, dass der Thread blockiert war, mit violetten Intervallen, die einen laufenden Systemaufruf markieren.1 Das Auswählen eines Intervalls hob mehr als nur das angeklickte Segment hervor und visualisierte „one continuous write system call that spans both on and off-core time”: Undurchsichtige Segmente sind On-Core-Ausführung, durchscheinende Segmente sind Off-Core-Blockierung.1

Das neue Inspector-Panel verwandelt das in ein Urteil. Wie die Session festhält: „The Inspector gives us the exact arguments passed to this system call. We can see the target file descriptor, the memory address of the buffer, and most importantly, the size.”1 Die Größe war der eindeutige Beweis: Die App versuchte „trying to write over 1.7 gigabytes of data on the main thread.”1 Der Inspector zeigte auch die Kosten: Die einzelne Operation „took over 500 milliseconds, and almost 300 of those milliseconds were spent off-core waiting for the disk.”1 Der synchrone data.write-Aufruf war der Engpass. Das Einbetten von Kodieren und Schreiben in eine Swift-Task verschob es in den Concurrent-Thread-Pool, und ein Verifizierungs-Trace bestätigte, dass der Write-Systemaufruf nun auf einem Background-Thread erscheint, nicht auf dem Main-Thread.1 Das Inspector-Panel existiert, um synchrones Blockierungsverhalten wie Datei-I/O offenzulegen, das ein reiner CPU-Profiler nicht sehen kann.

Wichtigste Erkenntnisse

Für iOS- und macOS-Ingenieure:

  • Öffnen Sie zuerst den Time Profiler und lesen Sie während des Hängers die CPU-Last des Main-Threads; hohe CPU-Last führt Sie zu Top Functions, untätige CPU zu System Trace und dem Inspector.1
  • Verwenden Sie Top Functions, wenn ein Flame Graph keinen einzelnen Übeltäter zeigt; es führt verstreuten Laufzeit-Overhead nach Self-Weight zusammen, sodass die teuerste Funktion sichtbar wird.1

Für Teams, die Performance-Korrekturen ausliefern:

  • Verifizieren Sie jede Änderung mit Run Comparisons, gefiltert auf dasselbe os_signpost-Intervall, und lesen Sie die rot/grüne Differenz, statt einem Nebeneinander mit bloßem Auge zu vertrauen.1
  • Vergleiche werden im Dokument gespeichert und stapeln mehrere Läufe, sodass der Nachweis einer Korrektur zur Begutachtung mit dem Trace mitreist.1

Für Arbeit an Nebenläufigkeit und Reaktionsfähigkeit:

  • Greifen Sie zum Swift executors-Instrument, wenn Arbeit um den Main Actor kämpft; es zeigt, ob eine Task auf dem Main Actor, dem globalen Concurrent Executor oder einem benutzerdefinierten Executor lief.1
  • Profilieren Sie immer einen Release-Build, da Debug-Builds irreführende Daten erzeugen.1

FAQ

Was ist der Top-Functions-Modus in Instruments 27?

Top Functions ist ein neuer Analysemodus im Time Profiler. Er verwirft die Aufrufhierarchie und führt jede verstreute Instanz einer Funktion in einem Block zusammen, gerangt nach Self-Weight, der Zeit, die direkt mit der Ausführung von Instruktionen in dieser Funktion verbracht wurde. Er beantwortet eine Frage, mit der sich ein Flame Graph schwertut: welche konkreten Funktionen die meisten Zyklen insgesamt verbrannten, wenn ihre Kosten über viele aufrufende Zweige zersplittert sind, etwa bei Swift-Laufzeitfunktionen und Hilfsroutinen.1

Wie funktionieren Run Comparisons in Instruments?

Run Comparisons, in der Session als neu in Instruments beschrieben, berechnet die exakte Leistungsdifferenz zwischen einem Baseline-Trace und einem optimierten Trace. Es ordnet jede Funktion über die beiden Läufe hinweg zu, berechnet die Differenz für jeden Knoten im Stack und sortiert nach Performance-Unterschied, wobei Regressionen rot und Verbesserungen grün eingefärbt werden. Für einen sauberen Vergleich filtern Sie beide Läufe auf dasselbe os_signpost-Intervall, wählen eine Spur und wählen die Baseline aus einem Dropdown; Vergleiche werden im Dokument gespeichert.1

Was zeigt das Swift-executors-Instrument?

Das Swift executors-Instrument in Instruments 27 visualisiert den Main Actor, den globalen Concurrent Executor und beliebige benutzerdefinierte Executors in Ihrem Prozess. Es lässt Sie sehen, auf welchem Executor eine bestimmte Swift-Task lief, sodass Sie Main-Actor-Konkurrenz erkennen. In der Session machte es renderThumbnail-Tasks sichtbar, die auf dem Main Actor feststeckten, weil aus SwiftUI aufgerufener Code den Main-Actor-Kontext geerbt hatte; das Verlagern auf den globalen Executor beseitigte den Hänger.1

Wie findet man einen Hänger, der durch Datei-I/O verursacht wird?

Wenn die Oberfläche einfriert, die CPU-Last des Main-Threads aber niedrig ist (in der Session um die 20 Prozent), ist der Thread blockiert und wartet auf eine Systemressource, statt langsamen Code auszuführen. Wechseln Sie zum System-Trace-Template und wählen Sie das Systemaufruf-Intervall; das neue Inspector-Panel zeigt die exakten Argumente des Syscalls, einschließlich File-Descriptor, Pufferadresse und Schreibgröße, sowie On-Core- gegenüber Off-Core-Zeit. In der Demo legte es einen synchronen 1,7-GB-Schreibvorgang auf dem Main-Thread offen.1


Der Diagnoseablauf, den diese Session vermittelt, passt zur SwiftUI-Seite der Reaktionsfähigkeit in SwiftUI-Performance und Interop in iOS 27 und zur Mess-Denkweise in Der blinde Fleck der Performance. Der Nebenläufigkeitsschritt, der den Main-Actor-Hänger beseitigte, sitzt innerhalb des breiteren Modells, das in Swift-6.2-Nebenläufigkeit in der Praxis behandelt wird. Der zentrale Hub der gesamten Serie ist die Apple-Ecosystem-Serie.

Referenzen


  1. Apple, WWDC 2026 Session 268, Profile, fix, and verify: Improve app responsiveness with Instruments. Quelle für den Diagnoseablauf (zuerst der Time Profiler, die CPU-Last des Main-Threads lenkt die Untersuchung), die Hinweise zu Release-Build und Swift-Concurrency-Template, das os_signpost/OSSignposter-Points-of-Interest-Intervall, das neue Inspector-Panel, das Sampling-Modell von Call Tree und Flame Graph (Standardrate eine Millisekunde, Self-Weight), den Top Functions-Analysemodus und den swift_project_boxed_opaque_existential-Befund, Run Comparisons („New in Instruments”) mit rot/grünen Differenzen und im Dokument gespeicherten Vergleichstabs, das Swift executors-Instrument (Main Actor, globaler Concurrent Executor, benutzerdefinierte Executors) und die mit dem @concurrent-Attribut behobene renderThumbnail-Main-Actor-Konkurrenz sowie die System-Trace- und Inspector-Diagnose eines synchronen 1,7-GB-Schreibvorgangs (File-Descriptor, Pufferadresse, Schreibgröße, On-Core- gegenüber Off-Core-Zeit, über 500 ms mit fast 300 ms off-core). 

Verwandte Beiträge

Was Apples Performance-Team im WWDC26-Lab gesagt hat

Apples Power-&-Performance-Team beantwortete auf der WWDC26 live Fragen von Entwicklern. Praxisnahe Hinweise zu MetricKi…

12 Min. Lesezeit

MetricKit neu gebaut: Zustandsbewusste Telemetrie in iOS 27

MetricKit wurde in iOS 27 neu gebaut: asynchrone Metrik- und Diagnose-Streams, Codable-Berichte und ein StateReporting-F…

11 Min. Lesezeit

Von 76 auf 100: Ein perfekter Lighthouse-Score

Wie eine persönliche Portfolio-Website von einem mobilen Lighthouse-Performance-Score von 76 mit 0,493 CLS zu einem perf…

6 Min. Lesezeit