Swift-Kompilierungsengpässe auf einem Cloud-Mac diagnostizieren
Wenn die Kompilierung eines Swift-Moduls nicht mehr nur einige Dutzend Sekunden, sondern zunehmend mehrere Minuten dauert, sollte der Code nicht sofort umgeschrieben oder die Parallelisierung erhöht werden. In einer entfernten Umgebung fließen auch die Auflösung von Abhängigkeiten, Build-Skripte, das Linken und die Typprüfung in die von Xcode ausgewiesene Gesamtzeit ein. Für eine belastbare Analyse werden zunächst Toolchain und Eingaben auf dem Cloud-Mac fixiert. Anschließend lässt sich der Engpass anhand von Messdaten auf Phasenebene bis auf einzelne Quelldateien und Ausdrücke eingrenzen.
Zuerst eine reproduzierbare Messbasis schaffen
Wählen Sie einen Zeitraum, in dem keine anderen Build-Aufgaben ausgeführt werden, und fixieren Sie Code-Commit, Scheme, Configuration, SDK und Zielarchitektur. Protokollieren Sie xcodebuild -version, den aktuellen Commit und den tatsächlich ausgeführten Befehl. Ein einzelner Clean Build darf nicht direkt mit einem einzelnen inkrementellen Build verglichen werden, da beide unterschiedliche Fragestellungen beantworten.
mkdir -p "$HOME/build-audit"
xcodebuild -version > "$HOME/build-audit/toolchain.txt"
git rev-parse HEAD > "$HOME/build-audit/commit.txt"
set -o pipefail
/usr/bin/time -l xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-showBuildTimingSummary \
build \
2>&1 | tee "$HOME/build-audit/baseline.log"
Führen Sie zunächst drei gewöhnliche inkrementelle Builds direkt nacheinander aus und prüfen Sie, ob die Laufzeitschwankungen stabil sind. Soll ein Clean Build gemessen werden, muss ausdrücklich clean ausgeführt oder ein separater DerivedData-Pfad verwendet werden. Vor und nach der Optimierung ist dabei exakt dieselbe Bereinigungsmethode anzuwenden.
Der schnellste Einzelwert reicht nicht als Grundlage für eine Schlussfolgerung. Verglichen werden sollte der Median mehrerer Durchläufe unter identischen Bedingungen. Bewahren Sie außerdem das Protokoll des langsamsten Durchlaufs auf, um sporadisch ausgeführte Skripte, Netzwerkanfragen oder Ressourcenkonflikte untersuchen zu können.
Die Untersuchungsrichtung aus der Phasenübersicht ableiten
Die Build Timing Summary weist unter anderem die Zeiten für Kompilierung, Linken, Ressourcenverarbeitung und Skripte aus. Suchen Sie zuerst nach regelmäßig auftretenden Phasen mit dem größten Zeitanteil, statt nur die Gesamtzeit in der letzten Zeile zu betrachten.
| Beobachtung | Zuerst prüfen | Häufige Fehleinschätzung |
|---|---|---|
| SwiftCompile nimmt dauerhaft den größten Anteil ein | Typprüfung, Größe einzelner Dateien, Verhalten der Batch-Kompilierung | Langsames Linken wird vermutet |
| Run Script wird bei jedem Build ausgeführt | Ein- und Ausgabedateien, interner Suchbereich des Skripts | Nur die Maschinenparallelität wird erhöht |
| Dauer der Abhängigkeitsauflösung schwankt | Lockdatei, Repository-Zugriff, wiederholte Auflösung | Wird für ein Swift-Kompilierungsproblem gehalten |
| Link-Phase fällt deutlich auf | Link-Eingaben, Debug-Symbole, doppelte Bibliotheken | Geschäftslogik-Ausdrücke werden umgeschrieben |
Prüfen Sie außerdem, ob die Skripte ihre Ein- und Ausgaben deklarieren. Ein Skript ohne definierte Abhängigkeitsgrenzen kann bei jedem inkrementellen Build ausgeführt werden und dadurch die Wirkung von Quellcodeoptimierungen verdecken. Enthält ein Skript Downloads, sollten Netzwerkwartezeit und lokale Rechenzeit getrennt gemessen werden.
Langsame Funktionen und Ausdrücke lokalisieren
Nachdem der Engpass eindeutig der Swift-Kompilierung zugeordnet wurde, können vorübergehend Diagnoseparameter für das Frontend ergänzt werden. Diese lassen sich in OTHER_SWIFT_FLAGS einer eigenen Diagnose-Configuration setzen, damit die reguläre Teamkonfiguration unverändert bleibt.
-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking
Führen Sie denselben Build-Befehl erneut aus und speichern Sie dabei auch die Standardfehlerausgabe. Das Protokoll enthält üblicherweise Laufzeiten, Dateipositionen sowie die zugehörigen Funktionen oder Ausdrücke. Ausgabeformat und Schwellenwerte können sich zwischen Xcode-Toolchains ändern. Auswertungsskripte dürfen daher nicht voraussetzen, dass die Anzahl der Spalten dauerhaft gleich bleibt.
Was zuerst optimiert werden sollte
Sortieren Sie die Ergebnisse zunächst nach Laufzeit und fassen Sie sie anschließend nach Dateien zusammen. Ein mäßig teurer Hotspot, der mehrere Hundert Mal kompiliert wird, kann relevanter sein als eine einzelne extrem langsame Funktion. Typische Kostentreiber sind überlange generische Ketten, verschachtelte Closures, einzelne Ausdrücke mit vielen Verzweigungen und Collection-Transformationen, bei denen der Compiler mehrere Zwischentypen gleichzeitig ableiten muss.
Bearbeiten Sie pro Änderung nur eine Problemklasse: beispielsweise einen Ausdruck aufteilen, Zwischenergebnisse explizit typisieren oder eine große Funktion in kleinere Funktionen mit klaren Grenzen zerlegen. Werden Build-Einstellungen und Quellcode gleichzeitig geändert, lässt sich der erzielte Nutzen keiner Ursache zuverlässig zuordnen.
Die Ursache mit einer minimalen Änderung überprüfen
Verknüpft ein Codeabschnitt Filterung, Mapping, Dictionary-Erstellung und die Verarbeitung optionaler Werte in einem einzigen Ausdruck, können die Zwischenergebnisse zunächst benannt und explizit typisiert werden. Das Ziel ist nicht möglichst kurzer Quellcode, sondern eine kleinere Menge von Constraints, die der Type-Checker gleichzeitig lösen muss.
let validItems: [Item] = items.filter { $0.isValid }
let identifiers: [String] = validItems.map(\.identifier)
let result: [String: Item] = Dictionary(
uniqueKeysWithValues: zip(identifiers, validItems)
)
Messen Sie nach der Änderung erneut auf einem Branch, der gegenüber dem Basis-Commit ausschließlich diese eine Anpassung enthält. Prüfen Sie mindestens drei Punkte: Ist die Laufzeit des betreffenden Ausdrucks im Hotspot-Protokoll gesunken? Ist die Phase SwiftCompile kürzer geworden? Verbessert sich die gesamte Build-Zeit über mehrere Durchläufe hinweg stabil? Ändert sich nur die Gesamtzeit, während der Hotspot unverändert bleibt, sollten weiterhin Caches und Hintergrundaufgaben untersucht werden, statt die Verbesserung der Quellcodeänderung zuzuschreiben.
Die Diagnose in einen wartbaren Abnahmeprozess überführen
Diagnoseparameter sollten nicht dauerhaft für sämtliche Builds aktiviert werden. Robuster ist eine eigenständige Performance-Prüfung, die bei Bedarf ausgeführt wird und Toolchain, Commit, Befehl und Zusammenfassung archiviert. Protokolle können lokale Pfade, die Repository-Struktur oder expandierte Umgebungsvariablen enthalten und müssen daher vor dem Hochladen bereinigt werden.
Für jede Untersuchung sollten folgende Informationen gespeichert werden:
- Xcode- und Swift-Version.
- Git-Commit und Build-Konfiguration.
- Eindeutige Kennzeichnung als Clean Build oder inkrementeller Build.
- Unveränderte Rohzeiten aus mindestens drei Durchläufen.
- Phasenübersicht und wichtigste Hotspots im Quellcode.
- Einziger Unterschied zwischen vorher und nachher sowie das Verfahren zum Zurücksetzen.
Schwanken die Ergebnisse auf dem Cloud-Mac deutlich, prüfen Sie vor einer erneuten Messung, ob parallele Builds, Indizierungsaufgaben oder zurückgebliebene Skripte ausgeführt werden. MiniRent stellt dedizierte physische Nodes bereit. Vom Benutzer auf demselben Gerät gestartete Prozesse konkurrieren jedoch weiterhin um CPU, Arbeitsspeicher und Datenträger. Belastbare Schlussfolgerungen entstehen durch kontrollierte Eingaben, reproduzierbare Befehle und vollständig aufbewahrte Messdaten – nicht durch einen einzelnen Build, der zufällig schneller erscheint.
Häufig gestellte Fragen
Warum reicht die gesamte Xcode-Buildzeit nicht als Messwert?
Sie vermischt Abhängigkeitsauflösung, Cache-Zustand, Skripte, Kompilierung und Linken. Vergleichen Sie bei festem Commit und gleicher Xcode-Version zunächst die einzelnen Phasen.
Sollten Swift-Zeitdiagnosen dauerhaft aktiviert bleiben?
Nein. Sie erzeugen umfangreiche Protokolle und können sich mit der Toolchain ändern. Aktivieren Sie sie nur für kontrollierte Messungen und entfernen Sie sie danach wieder.
Dedizierte Cloud-Macs
Build-Aufgaben auf einem dedizierten physischen Knoten ausführen
Wählen Sie MiniRents M4 oder MiniRents M4 Pro zur Tages-, Wochen-, Monats- oder Quartalsmiete und wählen Sie den Knoten passend zu Ihrem Team und dem Standort Ihres Code-Repositorys. Die Geräte sind dedizierte physische Rechner, keine virtuellen Maschinen.