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.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
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. skill_type: discipline 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
- Profile with Swift Concurrency template
- Look at main thread → "Swift Tasks" lane
- Find long blue bars (task executing on main)
- Check if work could be offloaded
Solution patterns:
// ❌ 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
- Enable "Swift Actors" instrument
- Look for serialized access patterns
- Red = waiting, Blue = executing
- High red:blue ratio = contention problem
Solution patterns:
// ❌ 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
- Look for gaps in task execution across all threads
- Check for blocking primitives
- Replace with async equivalents
Common culprits:
// ❌ 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
- Inspect task priorities in Instruments
- Follow wait chains
- Ensure critical paths use appropriate priority
// ✅ 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:
-
Is work actually async?
- Look for suspension points (
await) - Sync code in async function still blocks
- Look for suspension points (
-
Holding locks across await?
// ❌ Deadlock risk mutex.withLock { await something() // Never! } -
Tasks in tight loops?
// ❌ 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) } } } -
DispatchSemaphore in async context?
- Always unsafe — use
withCheckedContinuationinstead
- Always unsafe — use
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 groupsos_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
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