Build Performance Optimization
Overview
Systematic Xcode build performance analysis and optimization. Core principle: Measure before optimizing, then optimize the critical path first.
When to Use This Skill
- Build times have increased significantly
- Incremental builds taking too long
- Want to analyze Build Timeline
- Need to identify slow-compiling Swift code
- Optimizing CI/CD build times
- Build performance regression investigation
- Enabling Xcode 26 compilation caching
- Reducing module variants in explicitly built modules
- Understanding the three-phase build process (scan โ modules โ compile)
Quick Win: Run the Agent First
For automated scanning and quick wins:
/axiom:optimize-build
The build-optimizer agent scans for common issues and provides immediate fixes. Use this skill for deep analysis.
The Build Performance Workflow
Step 1: Measure Baseline (Required)
Why: You can't improve what you don't measure. Baseline prevents placebo optimizations.
xcodebuild clean build -scheme YourScheme
time xcodebuild build -scheme YourScheme
Product โ Perform Action โ Build with Timing Summary
Record:
- Total build time
- Incremental build time (change one file, rebuild)
- Which phase takes longest (compilation vs linking vs scripts)
Example baseline:
Clean build: 247 seconds
Incremental (1 file change): 12 seconds
Longest phase: Compile Swift sources (189s)
Step 2: Analyze Build Timeline (Xcode 14+)
Access:
- Build your project (Cmd+B)
- Open Report Navigator (Cmd+9)
- Select latest build
- Show Assistant Editor (Cmd+Option+Return)
- Build Timeline appears alongside build log
What to look for:
Critical Path (The Build's Speed Limit)
The critical path is the shortest possible build time with unlimited CPU cores. It's defined by the longest chain of dependent tasks.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Critical Path: A โ B โ C โ D (120s) โ
โ โ
โ Task A: 30s โโโโโโโโโโ โ
โ Task B: 40s โโโ D: 20s โ
โ Task C: 30s โโโโโโโโโโ โ
โ โ
โ Even with 100 CPUs, build takes 120s โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Goal: Shorten the critical path by breaking dependencies.
Timeline Red Flags
Empty vertical space: Tasks waiting for inputs
Timeline:
โโโโโโโโโโโโโโโโโโโโโโโโ โ Bad: idle cores waiting
โโโโโโโโโโโโโโโโโโโโโโโโ โ Good: continuous work
Long horizontal bars: Slow individual tasks
Task A: โโโโโโโโโโโโโโโโโโโโ (45 seconds) โ Investigate
Task B: โโโ (3 seconds) โ Fine
Serial target builds: Targets waiting unnecessarily
Framework: โโโโโโโโโโโโโโโโโโ โ Waiting
App: โโโโโโโโโโโโโโโโโโ โ Delayed
Better (parallel):
Framework: โโโโโโโโ
App: โโโโโโโโโโโโโโโโ
Step 3: Identify Bottlenecks (Decision Tree)
Is compilation the slowest phase?
โโ YES โ Check type checking performance (Step 4)
โโ NO โ Is linking slow?
โโ YES โ Check link dependencies (Step 5)
โโ NO โ Are scripts slow?
โโ YES โ Optimize build phase scripts (Step 6)
โโ NO โ Check parallelization (Step 7)
Optimization Patterns
Pattern 1: Type Checking Performance (MEDIUM-HIGH IMPACT)
Symptom: "Compile Swift sources" takes >50% of build time.
Diagnosis:
Enable compiler warnings to find slow functions:
-warn-long-function-bodies 100
-warn-long-expression-type-checking 100
Build โ Xcode shows warnings:
MyView.swift:42: Function body took 247ms to type-check (limit: 100ms)
LoginViewModel.swift:18: Expression took 156ms to type-check (limit: 100ms)
Fix slow type checking:
func calculateTotal(items: [Item]) -> Double {
return items
.filter { $0.isActive }
.map { $0.price * $0.quantity }
.reduce(0, +)
}
func calculateTotal(items: [Item]) -> Double {
let activeItems: [Item] = items.filter { $0.isActive }
let prices: [Double] = activeItems.map { $0.price * $0.quantity }
let total: Double = prices.reduce(0, +)
return total
}
Common slow patterns:
- Complex chained operations without intermediate types
- Deeply nested closures
- Large literals (dictionaries, arrays)
- Operator overloading in complex expressions
Expected impact: 10-30% faster compilation for affected files.
Pattern 2: Build Phase Script Optimization (HIGH IMPACT)
Symptom: Build Timeline shows long script phases in Debug builds.
Common culprits:
- dSYM/Crashlytics uploads running in Debug
- Asset processing on every build
- Code generation scripts without caching
Fix: Make scripts conditional
firebase crashlytics upload-symbols
if [ "${CONFIGURATION}" = "Release" ]; then
firebase crashlytics upload-symbols
fi
Script Phase Sandboxing (Xcode 14+)
Enable to prevent data races and improve parallelization:
Build Settings โ User Script Sandboxing โ YES
Why: Forces you to declare inputs/outputs explicitly, enabling parallel execution.
Input Files:
$(SRCROOT)/input.txt
$(DERIVED_FILE_DIR)/checksum.txt
Output Files:
$(DERIVED_FILE_DIR)/output.html
Parallel Script Execution:
Build Settings โ FUSE_BUILD_SCRIPT_PHASES โ YES
โ ๏ธ WARNING: Only enable if ALL scripts have correct inputs/outputs declared. Otherwise you'll get data races.
Expected impact: 5-10 seconds saved per incremental debug build.
Pattern 3: Compilation Mode Settings (CRITICAL)
Symptom: Incremental builds recompile entire modules.
Check current settings:
grep "SWIFT_COMPILATION_MODE" project.pbxproj
Optimal configuration:
| Configuration |
Setting |
Why |
| Debug |
singlefile (Incremental) |
Only recompiles changed files |
| Release |
wholemodule |
Maximum optimization |
SWIFT_COMPILATION_MODE = wholemodule;
Debug: SWIFT_COMPILATION_MODE = singlefile;
Release: SWIFT_COMPILATION_MODE = wholemodule;
How to fix:
- Project โ Build Settings
- Filter: "Compilation Mode"
- Set Debug to "Incremental"
- Set Release to "Whole Module"
Expected impact: 40-60% faster incremental debug builds.
Pattern 4: Build Active Architecture Only (HIGH IMPACT)
Symptom: Debug builds compile for multiple architectures (x86_64 + arm64).
Check:
grep "ONLY_ACTIVE_ARCH" project.pbxproj
Fix:
| Configuration |
Setting |
Why |
| Debug |
YES |
Only build for current device (arm64 OR x86_64) |
| Release |
NO |
Build universal binary |
How to fix:
- Build Settings โ "Build Active Architecture Only"
- Set Debug to YES
- Keep Release as NO
Expected impact: 40-50% faster debug builds (half the architectures).
Pattern 5: Debug Information Format (MEDIUM IMPACT)
Symptom: Debug builds generating dSYMs unnecessarily.
Optimal configuration:
| Configuration |
Setting |
Why |
| Debug |
dwarf |
Embedded debug info, faster |
| Release |
dwarf-with-dsym |
Separate dSYM for crash reporting |
grep "DEBUG_INFORMATION_FORMAT" project.pbxproj
How to fix:
- Build Settings โ "Debug Information Format"
- Set Debug to "DWARF"
- Set Release to "DWARF with dSYM File"
Expected impact: 3-5 seconds saved per debug build.
Pattern 6: Target Parallelization (WWDC 2018-408)
Symptom: Build Timeline shows targets building sequentially when they could be parallel.
Check scheme configuration:
- Product โ Scheme โ Edit Scheme
- Build tab
- Check "Parallelize Build" checkbox
- Verify target order allows parallelization
Dependency graph example:
App โโโฌโโโ Framework A
โโโโ Framework B
Framework A โโโ Utilities
Framework B โโโ Utilities
Timeline (bad - serial):
Utilities: โโโโโโโโโโโโโโโโโโโโโโ
Framework A: โโโโโโโโโโโโโโโโโโโโโโ
Framework B: โโโโโโโโโโโโโโโโโโโโโโโโ
App: โโโโโโโโโโโโโโโโโโโโโโโโโโ
Timeline (good - parallel):
Utilities: โโโโโโโโ
Framework A: โโโโโโโโโโโโโโโโ
Framework B: โโโโโโโโโโโโโโโโ
App: โโโโโโโโโโโโโโโโโโโโ
Expected impact: Proportional to number of independent targets (e.g., 2 parallel targets = ~2x faster).
Pattern 7: Emit Module Optimization (Xcode 14+, Swift 5.7+)
What it is: Swift modules are produced separately from compilation, unblocking downstream targets faster.
Before (Xcode 13):
Framework: Compile โโโโโโโโโโโโ โ Emit Module โ
App: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Waiting for Framework compilation to finish
After (Xcode 14+):
Framework: Compile โโโโโโโโโโโโ
Emit Module โโโ
App: โโโโโโโโโโโโโโโโโ
โ
Starts as soon as module emitted
Automatic: No configuration needed, works in Xcode 14+ with Swift 5.7+.
Expected impact: Reduces idle time in multi-target builds by 20-40%.
Pattern 8: Eager Linking (Xcode 14+)
What it is: Linking can start before all compilation finishes if the module is ready.
Impact: Further reduces critical path in dependency chains.
Automatic: Works in Xcode 14+ automatically.
Pattern 9: Compilation Caching (Xcode 26+, CRITICAL)
What it is: Xcode 26 introduces compilation caching that reuses previously compiled artifacts across clean builds.
Build Settings:
Build Settings โ COMPILATION_CACHE_ENABLE_CACHING โ YES
How it works:
- Caches compilation results based on input file content and compiler flags
- Works across clean builds โ even after
xcodebuild clean, cached artifacts can be reused
- Significantly reduces CI/CD build times where clean builds are common
When to enable:
- CI/CD pipelines with frequent clean builds
- Teams sharing build artifacts
- Projects with stable dependencies
Verification:
xcodebuild build -scheme YourScheme \
COMPILATION_CACHE_ENABLE_CACHING