Eine lokale Messung mit Instruments zeigt „rund eine Sekunde bis zur ersten Darstellung“, doch nach dem Merge läuft dieselbe Version in Remote-Tests deutlich langsamer. Das Problem liegt meist nicht am Werkzeug, sondern an einem unklar abgegrenzten Messobjekt: Netzwerkwartezeit, JSON-Verarbeitung, Datenbankschreibvorgänge und UI-Aktualisierung fließen in eine einzige Gesamtdauer ein. Schon kleine Änderungen am Zustand des Rechners machen die Ergebnisse dadurch unvergleichbar. Zuverlässiger ist es, die relevanten Abläufe mit os_signpost in Intervalle zu unterteilen und sie anschließend mit xctrace auf einem Cloud Mac unter festgelegten Bedingungen wiederholt aufzuzeichnen.
Zuerst einen reproduzierbaren kritischen Ablauf definieren
Frage nicht zuerst, „wie schnell die gesamte App ist“, sondern wähle eine Benutzeraktion mit stabilen Start- und Endpunkten. Beim Öffnen einer Bestellseite kann die Messung beispielsweise mit dem Lesen des lokalen Caches beginnen und enden, sobald die erste Gruppe von Listenzellen ihre Datenbindung abgeschlossen hat. Dieser Ablauf lässt sich in drei Intervalle zerlegen:
| Intervall | Startpunkt | Endpunkt | Für ein Gate geeignet |
|---|---|---|---|
load_cache |
Cache-Lesevorgang starten | Strukturierte Daten zurückgeben | Geeignet |
decode_payload |
Dekodierung starten | Domänenobjekte erzeugen | Geeignet |
render_first_batch |
Erste Modelle übergeben | Layout der ersten Ansichten abschließen | Geeignet |
wait_remote_api |
Externe Anfrage starten | Antwort empfangen | Nur zur Diagnose |
Die Dauer externer Netzwerkzugriffe hängt von der Verbindung und vom Zustand des Servers ab. Sie sollte daher einen Merge nicht unmittelbar blockieren. Ein Gate sollte vorrangig Abläufe innerhalb des Prozesses abdecken, die feste Eingaben verwenden und sich reproduzierbar auslösen lassen.
Ein Intervall sollte nur eine Frage beantworten. Umfasst dieselbe Markierung gleichzeitig Netzwerk-, Datenträger- und Rendering-Vorgänge, lässt sich nach einer Überschreitung weiterhin nicht erkennen, welcher Teil korrigiert werden muss.
Stabile und eindeutig zuordenbare Messpunkte hinzufügen
Verwende feste Werte für subsystem und category, und setze Intervallnamen nicht aus Benutzerdaten zusammen. Werden parallele Abläufe gemessen, erhält jede fachliche Aktion eine eigene OSSignpostID. So werden die Start- und Endpunkte zweier benachbarter Aufgaben nicht versehentlich einander zugeordnet.
import os
enum PerfTrace {
static let log = OSLog(
subsystem: "com.example.mobile",
category: "CriticalPath"
)
static func begin(_ name: StaticString) -> OSSignpostID {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: name, signpostID: id)
return id
}
static func end(_ name: StaticString, id: OSSignpostID) {
os_signpost(.end, log: log, name: name, signpostID: id)
}
}
Auf der aufrufenden Seite muss das Intervall mit defer zuverlässig geschlossen werden. Andernfalls hinterlassen Fehlerpfade Intervalle mit einem Start-, aber ohne Endpunkt:
let traceID = PerfTrace.begin("decode_payload")
defer { PerfTrace.end("decode_payload", id: traceID) }
let models = try decoder.decode([Item].self, from: fixtureData)
Schreibe keine Tokens, Dateiinhalte oder Benutzerkennungen in den Signpost-Text. Die Leistungsnachweise werden mit den Build-Artefakten gespeichert. Deshalb sollten die Felder eine geringe Kardinalität haben und sich sicher archivieren lassen.
Ausführungsbedingungen für xctrace festlegen
Prüfe zunächst in der grafischen Oberfläche, ob der Name der Vorlage und die Intervalle tatsächlich sichtbar sind. Wechsle erst danach zur Kommandozeile. Die Test-App sollte feste Testdaten, ein festgelegtes Simulatormodell und dieselbe Build-Konfiguration verwenden. Beende vor jeder Aufzeichnung alte Prozesse, damit kein Zustand aus dem vorherigen Lauf erhalten bleibt.
set -euo pipefail
OUT="$PWD/perf-artifacts"
mkdir -p "$OUT"
xcrun xctrace record \
--template "Points of Interest" \
--time-limit 45s \
--output "$OUT/critical-path.trace" \
--launch -- "$APP_PATH" \
-PerfScenario first-batch \
-PerfFixture "$PWD/Fixtures/items.json"
xcrun xctrace export \
--input "$OUT/critical-path.trace" \
--toc > "$OUT/toc.xml"
XPath und Tabellenstruktur von xctrace export können sich je nach Werkzeugversion ändern. Das Auswertungsskript sollte deshalb zuerst prüfen, ob das erwartete Schema vorhanden ist, und erst danach die Intervalle lesen. Verwende ein von Spaltenpositionen abhängiges awk nicht als langfristige Schnittstelle. Parse bevorzugt das exportierte XML und bewahre bei Versionswechseln einen minimalen Trace für Vertragstests auf.
Bei der Ausführung in der Remote-Umgebung von LemonVM sollten zuerst xcodebuild -version, die Systemversion, die Build-Konfiguration und der Hash der Testdaten protokolliert werden. Ändert sich der Knoten oder die Toolchain, muss eine neue Baseline erstellt werden. Werte aus der alten und der neuen Umgebung sollten nicht direkt miteinander verglichen werden.
Grenzwerte aus mehreren Messläufen ableiten
Ein einzelner Lauf wird leicht durch das erstmalige Laden, Hintergrundindizierung oder den Temperaturzustand beeinflusst. Empfehlenswert ist ein Aufwärmlauf, gefolgt von mindestens fünf gültigen Aufzeichnungen. Das Gate vergleicht den Median und setzt zusätzlich eine großzügigere Obergrenze für den langsamsten Messwert.
Liegt der Median der Baseline bei 420 ms, kann die Warnschwelle auf das 1.15-Fache der Baseline, also 483 ms, gesetzt werden. Dieses Verhältnis sollte jedoch nicht unverändert auf alle Intervalle übertragen werden. Eine kleine Funktion mit einer Laufzeit von wenigen Dutzend Millisekunden hat ein anderes Schwankungsmodell als eine mehrere Sekunden dauernde Datenaufbereitung.
Das Bewertungsskript sollte mindestens drei Ergebnistypen unterscheiden:
- Der Trace kann nicht erzeugt werden: Fehler der Infrastruktur, keine Bewertung als Performance-Regression.
- Das Zielintervall fehlt: Fehler im Szenario oder in der Instrumentierung, der Test muss korrigiert werden.
- Gültige Messwerte überschreiten den Grenzwert: Das Performance-Gate schlägt fehl und die Nachweise werden archiviert.
Die Baseline-Datei sollte gemeinsam mit dem Code geprüft werden und Intervallname, Stichprobengröße, Median, zulässige Obergrenze, Toolchain-Version und Hash der Testdaten enthalten. Jede Anhebung eines Grenzwerts muss in der Commit-Beschreibung begründet werden. So wird verhindert, dass Grenzwerte nur steigen und nie wieder sinken.
Häufige Fehlalarme ausschließen
Ist nur der erste Lauf langsam und bleiben die folgenden Läufe stabil, liegt die Ursache meist in der Cache-Vorbereitung oder im dynamischen Linken. Kaltstart und aufgewärmter Ausführungspfad sollten dann als getrennte Gruppen behandelt werden. Sind alle Läufe langsam, werden anschließend die Codeänderungen geprüft. Ist nur ein einzelner Lauf auffällig, sollte zuerst kontrolliert werden, ob zu diesem Zeitpunkt eine Indizierung, ein Backup, ein zusätzlicher Simulator oder ein paralleler Build aktiv war.
Prüfe außerdem die folgenden Punkte:
- Daten aus Release- und Debug-Builds dürfen nicht vermischt werden;
- für unterschiedliche Systemversionen des Simulators müssen getrennte Baselines gepflegt werden;
- vor jedem Testlauf muss derselbe fachliche Zustand wiederhergestellt werden;
- Performance-Aufgaben und große Build-Aufgaben dürfen nicht parallel ausgeführt werden;
- Trace, Auswertungsergebnis und Standardfehlerausgabe müssen gemeinsam archiviert werden;
- wenn ein Intervall nicht ausgewertet werden kann, darf nicht standardmäßig
0 mseingetragen werden; - nach einem Timeout müssen die getestete App und verbliebene Aufzeichnungsprozesse beendet werden.
Wenn der Test eine grafische Sitzung voraussetzt, sollte vor Beginn der Aufgabe eine Sichtbarkeitsprüfung erfolgen. Werden nur reine Verarbeitungs- oder Speichervorgänge gemessen, sollte nach Möglichkeit ein separates Test-Target verwendet werden, um Störungen durch den UI-Zustand zu reduzieren.
Fehlerausgaben direkt verwertbar machen
Die Ausgabe des Gates darf nicht nur „Performance fehlgeschlagen“ melden. Sie sollte mindestens den Namen des Intervalls, den aktuellen Median, die Baseline, die prozentuale Veränderung, die Anzahl gültiger Messwerte und den Pfad zum Trace ausgeben. Beispiel:
FAIL render_first_batch
median_ms=512
baseline_ms=420
change=21.9%
samples=7
trace=perf-artifacts/run-06.trace
Nach Sichtung des Ergebnisses müssen Reviewer den zugehörigen Trace direkt herunterladen, das konkrete Intervall untersuchen und erkennen können, ob eine Regression im Anwendungscode oder eine Störung der Messumgebung vorliegt. Ein stabiles Performance-Gate soll nicht bei jedem Lauf exakt denselben Wert erzeugen. Es soll Änderungen bei identischen Eingaben, derselben Toolchain und gleichen Ausführungsbedingungen erklärbar und überprüfbar machen. Effektiver ist es, mit einem einzelnen, besonders relevanten Ablauf zu beginnen, dessen Schwankungen kontinuierlich zu beobachten und erst danach schrittweise weitere Intervalle hinzuzufügen, anstatt sofort Dutzende unzuverlässige Messwerte einzuführen.
Häufig gestellte Fragen
Soll ein Performance-Gate Mittelwert oder Median verwenden?
Nutze den Median mehrerer gültiger Durchläufe und ergänze ihn um ein hohes Perzentil oder einen festen Höchstwert. Ein Mittelwert reagiert zu stark auf einzelne Systemausreißer.
Ist eine fehlgeschlagene xctrace-Aufzeichnung bereits eine Performance-Regression?
Nein. Aufzeichnungsfehler, fehlende Intervalle und echte Grenzwertüberschreitungen benötigen getrennte Exit-Codes. Eine Regression darf erst nach einer gültigen Messung gemeldet werden.
Gehört jedes os_signpost-Intervall in die CI-Prüfung?
Nein. Automatisiere nur Abläufe mit stabilen Start- und Endpunkten sowie reproduzierbaren Eingaben. Stark interaktive oder von externen Live-Diensten abhängige Intervalle bleiben Diagnosewerte.
LemonVM Cloud Mac passend zur Laufzeit wählen
Lemon M4 und Lemon M4 Pro sind in Singapur, Tokio, Seoul, Hongkong und im Westen der USA verfügbar – mit Tages-, Wochen-, Monats- und Quartalsabrechnung.