Wenn dieselbe CI-Pipeline einmal nach 18 Minuten abgeschlossen ist, ein anderes Mal jedoch während der Testphase hängen bleibt und nach einem Neustart wieder funktioniert, sollte dies nicht vorschnell mit einer „gelegentlich langsamen Maschine“ erklärt werden. Auf einem Cloud-Mac konkurrieren Compiler, Linker, Simulatoren und Testprozesse gleichzeitig um den gemeinsamen Arbeitsspeicher. Allein anhand des Build-Ergebnisses lässt sich nicht erkennen, ob ein Codeproblem, zu hohe Parallelität oder anhaltende Speicherkomprimierung und Auslagerung die Ursache ist.
Zuerst ein reproduzierbares Beobachtungsfenster definieren
Für die Diagnose müssen Commit, Zustand des Abhängigkeits-Caches, Build-Befehl und Testumfang konstant bleiben. Wählen Sie einen Job, der das Problem zuverlässig auslöst, und protokollieren Sie Startzeit, Endzeit, Exit-Code sowie die Phase, in der der Fehler auftritt. Beim ersten Messlauf sollten Sie nicht gleichzeitig den Cache leeren und die Parallelität ändern, da sich sonst nicht feststellen lässt, welche Variable die Wirkung verursacht hat.
Ermitteln Sie zunächst die Ausgangswerte für physischen Speicher und Swap:
sysctl -n hw.memsize
sysctl vm.swapusage
memory_pressure -Q
vm_stat
hw.memsize gibt den physischen Arbeitsspeicher in Byte an. vm.swapusage zeigt die aktuelle Swap-Belegung. Seitengröße, komprimierte Seiten sowie Ein- und Auslagerungszähler aus vm_stat helfen dabei, die Entwicklung zu beurteilen. Entscheidend sind die Veränderungen zwischen Beginn und Ende des Jobs, nicht ein einzelner Momentwert, der als Grenzwert interpretiert wird.
Wenig freier Arbeitsspeicher bedeutet nicht automatisch Speichermangel. macOS nutzt verfügbaren Speicher aktiv zum Zwischenspeichern von Dateiseiten. Relevant wird es, wenn der Speicherdruck dauerhaft steigt, der Swap kontinuierlich wächst und zugleich längere Build-Zeiten oder Prozessabbrüche auftreten.
Während des Builds kontinuierlich messen
Führen Sie die Diagnosebefehle außerhalb des Build-Befehls aus, damit die Messdaten nicht zusammen mit einem beendeten Unterprozess verloren gehen. Das folgende Skript speichert alle 10 Sekunden den Speicherdruck, die Swap-Belegung und die 15 Prozesse mit dem höchsten RSS:
#!/bin/zsh
set -eu
out="${1:-memory-samples.log}"
while true; do
printf '
=== %s ===
' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$out"
memory_pressure -Q >> "$out" 2>&1
sysctl vm.swapusage >> "$out" 2>&1
ps -axo pid,ppid,rss,etime,command | sort -nrk3 | head -n 16 >> "$out"
sleep 10
done
Starten Sie zunächst zsh sample-memory.zsh in einem separaten Terminal und anschließend die Pipeline. Beenden Sie die Aufzeichnung nach Abschluss des Jobs. RSS wird üblicherweise in KB angegeben und entspricht nicht dem gesamten virtuellen Adressraum eines Prozesses. Der Wert reicht jedoch aus, um Compiler-, Simulator- oder Testprozesse zu identifizieren, deren Speicherbedarf kontinuierlich wächst.
Zusätzlich Phasenmarkierungen protokollieren
Geben Sie vor und nach der Abhängigkeitsauflösung, dem Kompilieren, dem Linken, dem Start der Simulatoren und der Testausführung jeweils die UTC-Zeit aus. So lassen sich Wendepunkte der Swap-Belegung konkreten Pipeline-Phasen zuordnen, statt lediglich kontextlose Systemmomentaufnahmen zu erhalten.
Ursachen des Speicherdrucks unterscheiden
Typische Muster lassen sich zunächst anhand der folgenden Tabelle einordnen:
| Beobachtung | Wahrscheinlichere Ursache | Nächster Schritt |
|---|---|---|
| Der RSS mehrerer Prozesse steigt während der Kompilierung gleichzeitig | Die Build-Parallelität überschreitet das Speicherbudget | Anzahl der Build-Jobs reduzieren und erneut testen |
| Der Speicherdruck steigt nach dem Start der Simulatoren sprunghaft an | Zu viele parallele Testziele | Anzahl der gleichzeitig ausgeführten Simulatoren begrenzen |
| Der RSS eines einzelnen Testprozesses wächst kontinuierlich | Speicherleck im Test oder im getesteten Code | Tests aufteilen und Prozess-Samples erfassen |
| Der Swap wächst kontinuierlich und die Laufzeiten schwanken stärker | Das System lagert häufig Speicherseiten aus und ein | Parallelität senken und dauerhaft laufende Hintergrundprozesse reduzieren |
| Der Speicherdruck bleibt normal, aber der Job wird beendet | Die Ursache liegt nicht zwingend beim Arbeitsspeicher | Exit-Code und Unified Log prüfen |
Nach einem unerwarteten Abbruch können Sie die relevanten Systemeinträge sofort durchsuchen:
log show --last 30m --style compact \
| grep -Ei 'memory pressure|memorystatus|killed process' \
> memory-events.log
Wenn das Protokoll keine Treffer enthält, beweist dies nicht, dass der Speicherzustand unauffällig war. Berücksichtigen Sie daher weiterhin den Verlauf der Messwerte und den Exit-Code der Pipeline. Auch ein einzelner Spitzenwert eines Prozesses ist noch kein Beleg für ein Speicherleck: Kompilierung und Linken erzeugen naturgemäß kurzfristige Spitzen. Verdächtiger ist ein anhaltender Anstieg, der nach Abschluss der jeweiligen Phase nicht zurückgeht.
Parallelität als konkretes Budget festlegen
Die Obergrenze der Parallelität sollte aus dem gemessenen Spitzenbedarf abgeleitet werden. Reservieren Sie zunächst etwa 20% des physischen Arbeitsspeichers für das System, Remote-Sitzungen und Protokollierungsprozesse. Der verbleibende Speicher steht Compilern und Simulatoren zur Verfügung. Wenn Kompilierung und Tests nicht gleichzeitig laufen, können Sie getrennte Budgets festlegen. Überschneiden sich diese Pipeline-Phasen, müssen beide Anforderungen gemeinsam berechnet werden.
Für Xcode-Builds bietet sich zunächst ein konservativer Test mit -jobs an:
set -o pipefail
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-jobs 4 \
build
Begrenzen Sie bei Simulatortests die Anzahl paralleler Ziele:
xcodebuild \
-workspace App.xcworkspace \
-scheme AppTests \
-parallel-testing-enabled YES \
-maximum-concurrent-test-simulator-destinations 2 \
test
Übernehmen Sie die Beispielwerte 4 und 2 nicht unverändert. Halbieren Sie zunächst die aktuelle Parallelität, führen Sie denselben Job mindestens dreimal hintereinander aus und erhöhen Sie den Wert anschließend schrittweise. Das Ziel besteht nicht darin, die CPU jede Sekunde vollständig auszulasten, sondern einen stabilen Durchsatz zu erreichen, ohne dass der Swap kontinuierlich wächst.
Optimierung mit einer Abnahmeliste abschließen
Ändern Sie pro Durchlauf nur einen Parameter und halten Sie die folgenden Ergebnisse fest:
- Wird derselbe Commit dreimal hintereinander erfolgreich verarbeitet?
- Wie hoch sind die maximale Swap-Belegung und die Differenz zwischen Beginn und Ende des Jobs?
- In welcher Pipeline-Phase steigt der Speicherdruck?
- Werden weiterhin Prozesse unerwartet beendet?
- Wie hoch sind Gesamtlaufzeit, Medianlaufzeit und Streuung der drei Ergebnisse?
- Verbessert sich nach dem Senken der Parallelität die Anzahl erfolgreich abgeschlossener Jobs pro Zeiteinheit?
Wenn eine geringere Parallelität einen einzelnen Build etwas verlangsamt, dafür aber Wiederholungen und zufällige Abbrüche verhindert, steigt der Gesamtdurchsatz in der Regel dennoch. Nachdem die Parameter bestätigt wurden, tragen Sie die Parallelitätswerte in die Pipeline-Konfiguration ein. Bewahren Sie das Messskript als zuschaltbares Diagnosewerkzeug auf, statt es dauerhaft mit hoher Frequenz auszuführen. Messen Sie erneut, wenn sich die Konfiguration ändert oder die Testmatrix erweitert wird. Nachdem Sie in der Konsole die aktuell verfügbaren Konfigurationen geprüft haben, sollten Sie das Budget außerdem mit demselben Referenzjob neu bestimmen.
Häufig gestellte Fragen
Reicht freier Speicher zur Diagnose eines Cloud Mac aus?
Nein. Speicherdruck, komprimierter Speicher, Swap-Zuwachs und Prozess-RSS müssen gemeinsam bewertet werden. Wenig freier Speicher ist unkritisch, solange macOS Cache-Seiten ohne dauerhaftes Swapping zurückgewinnt.
Wie prüfe ich eine niedrigere Xcode-Parallelität?
Führen Sie denselben Commit mit identischem Abhängigkeitscache und Testsatz mindestens dreimal aus. Vergleichen Sie Swap-Spitze, Prozessabbrüche, Laufzeit und Streuung vor und nach der Änderung.
Dedizierter physischer Cloud-Mac
Reproduzierbare Entwicklungsumgebungen auf dedizierten physischen Knoten bereitstellen
Wählen Sie eine Konfiguration, eine Laufzeit und einen von fünf Knoten für Xcode-Builds, automatisierte Tests, Remote-Entwicklung oder Modellinferenz.