Guide d’ingénierie

Diagnostiquer les lenteurs de compilation Swift sur un Mac distant

Diagnostiquer les lenteurs de compilation Swift sur un Mac distant

Lorsqu’un module Swift passe progressivement de quelques dizaines de secondes à plusieurs minutes de compilation, il ne faut pas commencer par réécrire le code ni augmenter le parallélisme. Dans un environnement distant, la résolution des dépendances, les scripts de build, l’édition de liens et la vérification des types contribuent tous à la durée totale indiquée par Xcode. Une démarche efficace consiste à figer la chaîne d’outils et les entrées sur le Mac distant, à recueillir des mesures pour chaque phase, puis à circonscrire le problème jusqu’aux fichiers sources et expressions concernés.

Établir d’abord une référence de mesure reproductible

Choisissez une période pendant laquelle aucune autre tâche de build ne s’exécute, puis fixez le commit, le Scheme, la Configuration, le SDK et l’architecture cible. Consignez la sortie de xcodebuild -version, le commit courant et la commande réellement exécutée. Ne comparez pas directement un build propre avec un build incrémental : ils répondent à des questions différentes.

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"

Commencez par lancer trois builds incrémentaux ordinaires à la suite afin de vérifier si les variations de durée restent stables. Pour mesurer un build propre, exécutez explicitement clean ou utilisez un chemin DerivedData distinct, en veillant à appliquer exactement la même méthode de nettoyage avant et après l’optimisation.

Le meilleur résultat d’une exécution isolée ne permet pas de tirer de conclusion. Comparez la médiane de plusieurs exécutions réalisées dans les mêmes conditions et conservez également le journal de la plus lente afin d’identifier d’éventuels scripts intermittents, requêtes réseau ou conflits de ressources.

Utiliser le récapitulatif des phases pour orienter l’analyse

Le Build Timing Summary détaille notamment les phases de compilation, d’édition de liens, de traitement des ressources et d’exécution des scripts. Recherchez d’abord les postes récurrents qui représentent la plus grande part du temps, plutôt que de vous concentrer uniquement sur la durée totale affichée à la dernière ligne.

Symptôme Vérifications prioritaires Erreur d’interprétation fréquente
SwiftCompile reste longtemps en tête Vérification des types, taille des fichiers, comportement de la compilation par lots Conclure à tort que l’édition de liens est lente
Run Script s’exécute à chaque fois Fichiers d’entrée et de sortie, périmètre analysé par le script Se contenter d’augmenter le parallélisme de la machine
La durée de résolution des dépendances varie fortement Fichier de verrouillage, accès aux dépôts, répétition éventuelle de la résolution L’attribuer à la compilation Swift
La phase Link ressort nettement Entrées de l’éditeur de liens, symboles de débogage, bibliothèques en double Réécrire les expressions du code métier

Vérifiez également que les scripts déclarent leurs entrées et leurs sorties. Sans limites de dépendance explicites, un script peut être relancé à chaque build incrémental et masquer les gains obtenus sur le code source. Si un script effectue des téléchargements, mesurez séparément l’attente réseau et le calcul local.

Repérer les fonctions et expressions lentes

Après avoir confirmé que le goulot d’étranglement se situe dans la compilation Swift, ajoutez temporairement les options de diagnostic du frontend. Vous pouvez les définir dans OTHER_SWIFT_FLAGS au sein d’une Configuration réservée au diagnostic, afin de ne pas affecter la configuration quotidienne de l’équipe.

-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking

Relancez exactement la même commande de build et enregistrez également la sortie d’erreur standard. Le journal indique généralement la durée, l’emplacement dans le fichier ainsi que la fonction ou l’expression concernée. Comme le format de sortie et les seuils peuvent varier d’une chaîne d’outils Xcode à l’autre, les scripts d’analyse ne doivent pas supposer que le nombre de colonnes restera toujours identique.

Éléments à traiter en priorité

Triez d’abord les résultats par durée, puis regroupez-les par fichier. Un point chaud de coût moyen recompilé des centaines de fois peut mériter davantage d’attention qu’une seule fonction extrêmement lente. Les constructions souvent coûteuses comprennent les longues chaînes de génériques, les fermetures imbriquées, les expressions uniques comportant de nombreuses branches et les transformations de collections qui obligent le compilateur à inférer simultanément plusieurs types intermédiaires.

À chaque modification, ne traitez qu’une seule catégorie de problème : décomposer une expression, déclarer explicitement le type des valeurs intermédiaires ou scinder une grande fonction en petites fonctions aux responsabilités bien délimitées. Ne modifiez pas simultanément les réglages de compilation et le code source, faute de quoi il sera impossible d’identifier l’origine du gain.

Valider la cause avec une modification minimale

Si un bloc de code enchaîne dans une seule expression le filtrage, la transformation, la construction d’un dictionnaire et le traitement de valeurs optionnelles, commencez par nommer les résultats intermédiaires et préciser leur type. L’objectif n’est pas de raccourcir le code source, mais de réduire le nombre de contraintes que le vérificateur de types doit résoudre en une seule fois.

let validItems: [Item] = items.filter { $0.isValid }
let identifiers: [String] = validItems.map(\.identifier)
let result: [String: Item] = Dictionary(
    uniqueKeysWithValues: zip(identifiers, validItems)
)

Une fois la modification terminée, refaites les mesures sur une branche qui ne diffère du commit de référence que par ce changement. Vérifiez au moins trois points : la durée de l’expression a-t-elle diminué dans le journal des points chauds, la phase SwiftCompile est-elle plus courte et la durée totale du build s’améliore-t-elle de façon stable sur plusieurs exécutions ? Si seule la durée totale change sans évolution du point chaud, poursuivez l’analyse du cache ou des tâches d’arrière-plan au lieu d’attribuer le résultat à la modification du code source.

Transformer le diagnostic en procédure de validation maintenable

Les options de diagnostic ne sont pas destinées à rester activées dans tous les builds. Il est plus fiable de créer une tâche distincte de contrôle des performances, à exécuter selon les besoins, puis d’archiver la chaîne d’outils, le commit, la commande et le récapitulatif. Les journaux peuvent contenir des chemins locaux, la structure du dépôt ou des valeurs issues du développement de variables d’environnement ; anonymisez-les avant tout envoi.

Pour chaque analyse, conservez de préférence les éléments suivants :

  1. Les versions de Xcode et de Swift.
  2. Le commit Git et la configuration de build.
  3. Une indication explicite précisant s’il s’agit d’un build propre ou incrémental.
  4. Les durées brutes d’au moins trois exécutions.
  5. Le récapitulatif des phases et les principaux points chauds du code source.
  6. L’unique différence entre l’avant et l’après, ainsi que la méthode de retour arrière.

Si les résultats varient fortement sur le Mac distant, vérifiez d’abord si des builds parallèles, des tâches d’indexation ou d’anciens scripts sont encore en cours d’exécution, puis recommencez les mesures. MiniRent fournit des nœuds physiques dédiés, mais les tâches lancées par l’utilisateur sur un même appareil restent en concurrence pour le CPU, la mémoire et le disque. Une conclusion fiable repose sur des entrées contrôlées, des commandes reproductibles et des preuves intégralement conservées, et non sur un build isolé qui paraît plus rapide.

Questions fréquentes

Pourquoi le temps total d’un build Xcode ne suffit-il pas ?

Il additionne la résolution des dépendances, les caches, les scripts, la compilation et l’édition de liens. Il faut stabiliser l’environnement puis comparer chaque phase séparément.

Faut-il conserver les options de diagnostic Swift dans le projet ?

Non. Elles servent à une analyse ponctuelle, génèrent beaucoup de journaux et peuvent évoluer avec la chaîne d’outils. Retirez-les après avoir conservé les résultats utiles.

Mac cloud dédié

Exécutez vos builds sur un nœud physique dédié

Choisissez MiniRents M4 ou MiniRents M4 Pro à la journée, à la semaine, au mois ou au trimestre, puis sélectionnez un nœud selon votre équipe et l’emplacement de votre dépôt de code. Chaque appareil est une machine physique dédiée, et non une machine virtuelle.

Choisir un modèle et commander