Use when build times are slow, investigating build performance, analyzing Build Timeline, identifying type checking bottlenecks, enabling compilation caching, or optimizing incremental builds - comprehensive build optimization workflows including Xcode 26 compilation caching
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: axiom-build-performance description: Use when build times are slow, investigating build performance, analyzing Build Timeline, identifying type checking bottlenecks, enabling compilation caching, or optimizing incremental builds - comprehensive build optimization workflows including Xcode 26 compilation caching skill_type: discipline version: 2.0 last_updated: 2026-01-01 apple_platforms: iOS 14+, macOS 11+, iPadOS 14+, tvOS 14+, watchOS 7+, axiom-visionOS 1.0+ xcode_version: Xcode 14+ (Xcode 26+ for compilation caching and explicit modules) wwdc_sessions: [2018-408, 2022-110364, 2024-10171, 2025-247]
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.
# Clean build (eliminates all caching)
xcodebuild clean build -scheme YourScheme
# Measure time
time xcodebuild build -scheme YourScheme
# Or use Xcode UI
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:
// Add to Debug build settings → Other Swift Flags
-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:
// ❌ SLOW - Complex type inference (247ms)
func calculateTotal(items: [Item]) -> Double {
return items
.filter { $0.isActive }
.map { $0.price * $0.quantity }
.reduce(0, +)
}
// ✅ FAST - Explicit types (12ms)
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
# ❌ BAD - Runs in ALL configurations (adds 6+ seconds to debug builds)
#!/bin/bash
firebase crashlytics upload-symbols
# ✅ GOOD - Skip in Debug
#!/bin/bash
if [ "${CONFIGURATION}" = "Release" ]; then
firebase crashlytics upload-symbols
fi
# Example savings: 6.3 seconds per incremental debug build
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.
# Script phase with proper inputs/outputs
Input Files:
$(SRCROOT)/input.txt
$(DERIVED_FILE_DIR)/checksum.txt
Output Files:
$(DERIVED_FILE_DIR)/output.html
# Now Xcode knows dependencies and can parallelize safely
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:
# In project.pbxproj
grep "SWIFT_COMPILATION_MODE" project.pbxproj
Optimal configuration:
| Configuration | Setting | Why |
|---|---|---|
| Debug | singlefile (Incremental) | Only recompiles changed files |
| Release | wholemodule | Maximum optimization |
// ❌ BAD - Whole module in Debug
SWIFT_COMPILATION_MODE = wholemodule; // ALL configs
// ✅ GOOD - Incremental for Debug
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 |
# Check current
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:
# Build with caching enabled
xcodebuild build -scheme YourScheme \
COMPILATION_CACHE_ENABLE_CACHING=YES
# Check build log for cache information
Current limitations (Xcode 26):
- Swift Package Manager dependencies not yet cacheable
- CompileStoryboard, CompileXIB, DataModelCompile, Ld tasks not cacheable
- Cache requires time to populate on first run
Expected impact: 20-40% faster clean builds after initial cache population (up to 70%+ for favorable projects).
Pattern 10: Explicitly Built Modules (Xcode 16+, HIGH IMPACT)
What it is: Xcode splits module compilation into explicit build tasks instead of implicit on-demand compilation. Enabled by default for Swift in Xcode 26.
The Problem with Implicit Modules (Pre-Xcode 16):
When a compiler encounters an import, it builds the module on-demand:
Compile A.swift ─── needs UIKit ───→ (builds UIKit.pcm) ───→ continues
Compile B.swift ─── needs UIKit ───→ (waits for A to finish) ───→ uses cached
Compile C.swift ─── needs UIKit ───→ (waits) ───→ uses cached
Problems:
- One task blocks others waiting for the same module
- Non-deterministic: whoever gets there first builds it
- Build failures hard to reproduce (depends on task order)
Explicitly Built Modules Solution:
Xcode now separates compilation into three phases:
Phase 1: SCAN Phase 2: BUILD MODULES Phase 3: COMPILE
┌──────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ Scan A.swift │ │ Build UIKit.pcm │ │ Compile A.swift │
│ Scan B.swift │ → │ Build Foundation.pcm │ → │ Compile B.swift │
│ Scan C.swift │ │ Build SwiftUI.pcm │ │ Compile C.swift │
└──────────────────┘ └──────────────────────┘ └──────────────────┘
(fast) (parallel) (parallel)
Benefits:
- More reliable builds: Precise dependencies, deterministic build graphs
- More efficient scheduling: Build system knows exactly what's needed
- Better debugging: Debugger reuses built modules (no separate rebuild)
- Visible module tasks: See "Compile Clang Module" and "Compile Swift Module" in build log
Enable/Disable (if needed):
Build Settings → Explicitly Built Modules → YES (default in Xcode 26 for Swift)
Module Variants (WWDC 2024-10171)
The same module may be built multiple times with different settings:
Build Log:
Compile Clang module 'UIKit' (hash: abc123) ← Variant 1
Compile Clang module 'UIKit' (hash: def456) ← Variant 2
Compile Swift module 'UIKit' (hash: ghi789) ← Variant 3
Common causes of variants:
- Different preprocessor macros between targets
- Mixed C and Objective-C language modes
- Different C language versions (C11 vs C17)
- Disabling ARC on some targets
Diagnose variants:
- Build with Timing Summary:
Product → Perform Action → Build with Timing Summary - Filter build log: Type "modules report" in filter box
- View Clang and Swift module reports showing variant counts
Reduce variants (unify settings at project/workspace level):
# Check for macro differences
grep "GCC_PREPROCESSOR_DEFINITIONS" project.pbxproj
# Move target-specific macros to project level where possible
Project → Build Settings → Preprocessor Macros → [unify here]
Example (from WWDC 2024-10171):
Before: 4 UIKit variants (2 Swift × 2 Clang)
After: 2 UIKit variants (unified settings)
Impact: Fewer module builds = faster incremental builds
Expected impact: 10-30% faster builds by reducing duplicate module compilation.
Note: Swift Build (Xcode 26+): Xcode now uses Swift Build, Apple's open-source build engine. This provides more predictable builds, better SPM integration, and cross-platform support (Linux, Windows, Android). No configuration needed.
Measurement & Verification
Before and After Comparison
Required steps:
-
Baseline (before changes):
xcodebuild clean build -scheme YourScheme 2>&1 | tee baseline.log -
Apply ONE optimization at a time
-
Measure improvement:
xcodebuild clean build -scheme YourScheme 2>&1 | tee optimized.log -
Compare:
# Extract build time from logs grep "Build succeeded" baseline.log grep "Build succeeded" optimized.log
Example:
Baseline: Build succeeded (247.3 seconds)
Optimized: Build succeeded (156.8 seconds)
Improvement: 90.5 seconds (36.6% faster)
Build Timeline Visual Verification
Before optimization:
- Look for empty vertical space (idle cores)
- Long horizontal bars (slow tasks)
- Serial target builds
After optimization:
- Timeline should be more "filled"
- Shorter horizontal bars
- Parallel target builds
Critical path: Should be visibly shorter.
Real-World Optimization Examples
Example 1: Large iOS App (50+ source files)
Baseline:
- Clean build: 247 seconds
- Incremental (1 file): 12 seconds
Optimizations applied:
- Debug compilation mode: singlefile (saved 89s)
- Build Active Architecture: YES (saved 45s)
- Conditional dSYM upload script (saved 6.3s per incremental)
Result:
- Clean build: 156 seconds (36% faster)
- Incremental: 5.7 seconds (52% faster)
Example 2: Multi-Framework Project
Baseline:
- 5 frameworks built serially
- Total: 189 seconds
Optimizations applied:
- Enabled parallel builds in scheme
- Fixed unnecessary dependencies
- Emit module optimization (automatic in Xcode 14)
Result:
- Total: 94 seconds (50% faster)
- Critical path reduced from 189s to 94s
Common Pitfalls
Pitfall 1: Optimizing Without Measuring
Mistake: "I think this will help" → make change → no measurement.
Why bad: Placebo improvements, wasted time, actual regressions unnoticed.
Fix: Always measure before → change one thing → measure after.
Pitfall 2: Optimizing Release Builds for Speed
Mistake: Set Release to incremental compilation for "faster builds".
Why bad: Release builds should optimize for runtime performance, not build speed. You ship Release builds to users.
Fix: Only optimize Debug builds for speed. Keep Release optimized for runtime.
Pitfall 3: Breaking Dependencies for Parallelization
Mistake: Remove legitimate dependencies to "make builds parallel".
Why bad: Build errors, undefined behavior, race conditions.
Fix: Only parallelize truly independent targets. Use Build Timeline to identify safe opportunities.
Pitfall 4: Enabling FUSE_BUILD_SCRIPT_PHASES Without Sandboxing
Mistake: Enable parallel scripts but don't declare inputs/outputs.
Why bad: Data races, non-deterministic build failures, incorrect builds.
Fix: First enable ENABLE_USER_SCRIPT_SANDBOXING = YES, fix all errors, THEN enable FUSE_BUILD_SCRIPT_PHASES.
Troubleshooting
Problem: Builds Still Slow After Optimizations
Check:
- Did you clean before measuring? (
xcodebuild clean) - Are you measuring the right build? (Debug vs Release)
- Is your machine thermal throttling? (Activity Monitor → CPU tab)
- Are other apps using CPU? (Quit Xcode, Docker, VMs during measurement)
Problem: Build Timeline Shows No Parallelization
Check:
- Scheme → Parallelize Build checked?
- Are targets actually independent? (Check dependency graph)
- Do targets have unnecessary explicit dependencies?
Problem: Type Checking Warnings Don't Appear
Check:
- Added flags to correct configuration? (Debug, not Release)
- Syntax correct?
-warn-long-function-bodies 100(with hyphen) - Building the right scheme?
- Clean build to force recompilation
Advanced: Analyzing Build Logs
Extract Compilation Times
# Find slowest files to compile
xcodebuild -workspace YourApp.xcworkspace \
-scheme YourScheme \
clean build \
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" 2>&1 | \
grep ".[0-9]ms" | \
sort -nr | \
head -20
Output:
247.3ms MyViewModel.swift:42:1 func calculateTotal
156.8ms LoginView.swift:18:3 var body
89.2ms NetworkManager.swift:67:1 func handleResponse
...
Action: Add explicit types to slowest functions.
Extract Build Phase Times
# From build log
Build target 'MyApp' (project 'MyApp')
Compile Swift source files (128.4 seconds)
Link MyApp (12.3 seconds)
Run custom shell script (6.7 seconds)
Action: Optimize the longest phase first.
Checklist: Build Performance Audit
Before considering your build optimized:
Measurement
- Measured baseline (clean + incremental)
- Verified improvement in Build Timeline
- Documented baseline → optimized comparison
Compilation Settings
- Debug uses incremental compilation
- Build Active Architecture = YES (Debug only)
- Debug uses DWARF (not dSYM)
- Type checking warnings enabled
- Fixed slow type-checking functions (>100ms)
Parallelization
- Parallelize Build enabled in scheme
- No unnecessary target dependencies
- Build phase scripts are conditional (skip in Debug when possible)
- Enabled script sandboxing if using parallel scripts
Xcode 26+ (if applicable)
- Compilation caching enabled for CI/CD (
COMPILATION_CACHE_ENABLE_CACHING) - Checked module variants (Modules Report in build log, see Pattern 10)
- Unified build settings at project level to reduce module variants
- Explicitly Built Modules enabled (default for Swift in Xcode 26)
Resources
WWDC: 2018-408, 2022-110364, 2024-10171, 2025-247
Docs: /xcode/improving-the-speed-of-incremental-builds, /xcode/building-your-project-with-explicit-module-dependencies
Tools: Xcode Build Timeline (Xcode 14+), Build with Timing Summary (Product → Perform Action), Modules Report (Xcode 16+), Instruments Time Profiler
Remember: Build performance optimization is about systematic measurement and targeted improvements. Optimize the critical path first, measure everything, and verify improvements in the Build Timeline.
More by CharlesWiltgen
View allUse when porting OpenGL/DirectX to Metal - translation layer vs native rewrite decisions, migration planning, anti-patterns
Use when UI is slow, scrolling lags, animations stutter, or when asking 'why is my SwiftUI view slow', 'how do I optimize List performance', 'my app drops frames', 'view body is called too often', 'List is laggy' - SwiftUI performance optimization with Instruments 26 and WWDC 2025 patterns
Use for Core Location troubleshooting - no location updates, background location broken, authorization denied, geofence not triggering
Reference — Comprehensive SwiftUI navigation guide covering NavigationStack (iOS 16+), NavigationSplitView (iOS 16+), NavigationPath, deep linking, state restoration, Tab+Navigation integration (iOS 18+), Liquid Glass navigation (iOS 26+), and coordinator patterns