--- name: axiom-concurrency-profiling description: Use when profiling async/await performance, diagnosing actor contention, or investigating thread pool exhaustion. Covers Swift Concurrency Instruments template, task visualization, actor contention analysis, thread pool debugging. license: MIT metadata: version: "1.0.0" --- # Concurrency Profiling — Instruments Workflows Profile and optimize Swift async/await code using Instruments. ## When to Use ✅ **Use when:** - UI stutters during async operations - Suspecting actor contention - Tasks queued but not executing - Main thread blocked during async work - Need to visualize task execution flow ❌ **Don't use when:** - Issue is pure CPU performance (use Time Profiler) - Memory issues unrelated to concurrency (use Allocations) - Haven't confirmed concurrency is the bottleneck ## Swift Concurrency Template ### What It Shows | Track | Information | |-------|-------------| | **Swift Tasks** | Task lifetimes, parent-child relationships | | **Swift Actors** | Actor access, contention visualization | | **Thread States** | Blocked vs running vs suspended | ### Statistics - **Running Tasks**: Tasks currently executing - **Alive Tasks**: Tasks present at a point in time - **Total Tasks**: Cumulative count created ### Color Coding - **Blue**: Task executing - **Red**: Task waiting (contention) - **Gray**: Task suspended (awaiting) ## Workflow 1: Diagnose Main Thread Blocking **Symptom**: UI freezes, main thread timeline full 1. Profile with Swift Concurrency template 2. Look at main thread → "Swift Tasks" lane 3. Find long blue bars (task executing on main) 4. Check if work could be offloaded **Solution patterns**: ```swift // ❌ Heavy work on MainActor @MainActor class ViewModel: ObservableObject { func process() { let result = heavyComputation() // Blocks UI self.data = result } } // ✅ Offload heavy work @MainActor class ViewModel: ObservableObject { func process() async { let result = await Task.detached { heavyComputation() }.value self.data = result } } ``` ## Workflow 2: Find Actor Contention **Symptom**: Tasks serializing unexpectedly, parallel work running sequentially 1. Enable "Swift Actors" instrument 2. Look for serialized access patterns 3. Red = waiting, Blue = executing 4. High red:blue ratio = contention problem **Solution patterns**: ```swift // ❌ All work serialized through actor actor DataProcessor { func process(_ data: Data) -> Result { heavyProcessing(data) // All callers wait } } // ✅ Mark heavy work as nonisolated actor DataProcessor { nonisolated func process(_ data: Data) -> Result { heavyProcessing(data) // Runs in parallel } func storeResult(_ result: Result) { // Only actor state access serialized } } ``` **More fixes**: - Split actor into multiple (domain separation) - Use Mutex for hot paths (faster than actor hop) - Reduce actor scope (fewer isolated properties) ## Workflow 3: Thread Pool Exhaustion **Symptom**: Tasks queued but not executing, gaps in task execution **Cause**: Blocking calls exhaust cooperative pool 1. Look for gaps in task execution across all threads 2. Check for blocking primitives 3. Replace with async equivalents **Common culprits**: ```swift // ❌ Blocks cooperative thread Task { semaphore.wait() // NEVER do this // ... semaphore.signal() } // ❌ Synchronous file I/O in async context Task { let data = Data(contentsOf: fileURL) // Blocks } // ✅ Use async APIs Task { let (data, _) = try await URLSession.shared.data(from: fileURL) } ``` **Debug flag**: ``` SWIFT_CONCURRENCY_COOPERATIVE_THREAD_BOUNDS=1 ``` Detects unsafe blocking in async context. ## Workflow 4: Priority Inversion **Symptom**: High-priority task waits for low-priority 1. Inspect task priorities in Instruments 2. Follow wait chains 3. Ensure critical paths use appropriate priority ```swift // ✅ Explicit priority for critical work Task(priority: .userInitiated) { await criticalUIUpdate() } ``` ## Thread Pool Model Swift uses a **cooperative thread pool** matching CPU core count: | Aspect | GCD | Swift Concurrency | |--------|-----|-------------------| | Threads | Grows unbounded | Fixed to core count | | Blocking | Creates new threads | Suspends, frees thread | | Dependencies | Hidden | Runtime-tracked | | Context switch | Full kernel switch | Lightweight continuation | **Why blocking is catastrophic**: - Each blocked thread holds memory + kernel structures - Limited threads means blocked = no progress - Pool exhaustion deadlocks the app ## Quick Checks (Before Profiling) Run these checks first: 1. **Is work actually async?** - Look for suspension points (`await`) - Sync code in async function still blocks 2. **Holding locks across await?** ```swift // ❌ Deadlock risk mutex.withLock { await something() // Never! } ``` 3. **Tasks in tight loops?** ```swift // ❌ Overhead may exceed benefit for item in items { Task { process(item) } } // ✅ Structured concurrency await withTaskGroup(of: Void.self) { group in for item in items { group.addTask { process(item) } } } ``` 4. **DispatchSemaphore in async context?** - Always unsafe — use `withCheckedContinuation` instead ## Common Issues Summary | Issue | Symptom in Instruments | Fix | |-------|------------------------|-----| | MainActor overload | Long blue bars on main | `Task.detached`, `nonisolated` | | Actor contention | High red:blue ratio | Split actors, use `nonisolated` | | Thread exhaustion | Gaps in all threads | Remove blocking calls | | Priority inversion | High-pri waits for low-pri | Check task priorities | | Too many tasks | Task creation overhead | Use task groups | ## Safe vs Unsafe Primitives **Safe with cooperative pool**: - `await`, actors, task groups - `os_unfair_lock`, `NSLock` (short critical sections) - `Mutex` (iOS 18+) **Unsafe (violate forward progress)**: - `DispatchSemaphore.wait()` - `pthread_cond_wait` - Sync file/network I/O - `Thread.sleep()` in Task ## Resources **WWDC**: 2022-110350, 2021-10254 **Docs**: /xcode/improving-app-responsiveness **Skills**: axiom-swift-concurrency, axiom-performance-profiling, axiom-synchronization, axiom-lldb (interactive thread state inspection)