This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
250 lines
6.3 KiB
Markdown
250 lines
6.3 KiB
Markdown
---
|
|
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)
|