Performance Profiling
Systematic guide for profiling Apple platform apps using Instruments, Xcode diagnostics, and MetricKit. Covers CPU, memory, launch time, and energy analysis with actionable fix patterns.
When This Skill Activates
Use this skill when the user:
- Reports app hangs, stutters, or dropped frames
- Needs to profile CPU usage or find hot code paths
- Has memory leaks, high memory usage, or OOM crashes
- Wants to optimize app launch time
- Needs to reduce battery/energy impact
- Asks about Instruments, Time Profiler, Allocations, or Leaks
- Wants to add
os_signpost or performance measurement to code
- Is preparing for App Store review and needs performance validation
Decision Tree
What performance problem are you investigating?
│
├─ App hangs / unresponsive to taps / slow UI
│ └─ Read time-profiler.md
│
├─ Scroll or animation stutter / dropped frames / hitches
│ └─ Read hitches.md
│
├─ High memory / leaks / OOM crashes / growing footprint
│ └─ Read memory-profiling.md
│
├─ Slow app launch / time to first frame
│ └─ Read launch-optimization.md
│
├─ Battery drain / thermal throttling / background energy
│ └─ Read energy-diagnostics.md
│
├─ General "app feels slow" (unknown cause)
│ └─ Start with time-profiler.md, then memory-profiling.md
│
└─ Pre-release performance audit
└─ Read ALL reference files, use Review Checklist below
Quick Reference
| Problem |
Instrument / Tool |
Key Metric |
Reference |
| UI hangs > 250ms |
Time Profiler + Hangs |
Hang duration, main thread stack |
time-profiler.md |
| Scroll/animation hitches |
Animation Hitches template |
Hitch time ratio (< 5 ms/s good, > 10 critical) |
hitches.md |
| High CPU usage |
Time Profiler / CPU Profiler |
CPU % by function, call tree weight |
time-profiler.md |
| Memory leak |
Leaks + Memory Graph |
Leaked bytes, retain cycle paths |
memory-profiling.md |
| Memory growth |
Allocations |
Live bytes, generation analysis |
memory-profiling.md |
| Slow launch |
App Launch |
Time to first frame (pre-main + post-main) |
launch-optimization.md |
| Battery drain |
Energy Log / Power Profiler |
Energy Impact score, CPU/GPU/network |
energy-diagnostics.md |
| Thermal issues |
Activity Monitor |
Thermal state transitions |
energy-diagnostics.md |
| Network waste |
Network profiler |
Redundant fetches, large payloads |
energy-diagnostics.md |
The 8 Key Metrics (WWDC21 10181)
Apple enumerates exactly eight things to track for app performance, all coverable with five tools (Xcode Organizer, MetricKit, Instruments, XCTest, App Store Connect API):
| # |
Metric |
Threshold / signal |
Tool |
| 1 |
Battery usage |
Energy Gauge flags CPU use > 20% as High CPU, plus CPU Wake Overhead regions; top subsystems to watch: CPU, Networking, Location |
Energy Gauge → Time Profiler |
| 2 |
Launch time |
Time between icon tap and first frame rendered |
App Launch template, XCTApplicationLaunchMetric |
| 3 |
Hang rate |
Unresponsive to input for ≥ 250ms |
Hangs instrument, Organizer |
| 4 |
Memory |
Organizer charts peak memory and memory at suspension; a spike with no termination spike yet is your early warning |
Allocations, Leaks, VM Tracker |
| 5 |
Disk writes |
Exception report when the app writes > 1 GB in 24 hours (with stack trace + Insights annotations) |
File Activity template, XCTStorageMetric |
| 6 |
Scrolling |
Red bars in the Organizer scrolling chart = poor scroll experience, fix immediately |
Animation Hitches, XCTOSSignpostMetric.scrollDecelerationMetric |
| 7 |
Terminations |
Every termination forces a slow cold launch next time and loses user state |
MXAppExitMetric, Organizer |
| 8 |
MXSignposts |
Custom marked intervals for your critical code sections |
MetricKit |
The Organizer aggregates all of these from consented user devices across the last 16 app versions; the Regressions pane (Xcode 13) isolates every metric that increased significantly in the most recent version, in one place. The same data is available as JSON via the App Store Connect API (WWDC21 10181).
Process
1. Identify the Problem Category
Ask the user or inspect their description to classify the issue:
- Responsiveness: Hangs, stutters, animation drops
- Memory: Leaks, growth, OOM crashes
- Launch: Slow cold/warm start
- Energy: Battery drain, thermal throttling
2. Read the Appropriate Reference File
Each file contains:
- Which Instruments template to use
- Step-by-step profiling workflow
- How to interpret results
- Common fix patterns with code examples
3. Profile on Real Hardware
Always remind users:
- Profile on device, not Simulator (Simulator uses host CPU/memory)
- Use Release build configuration (optimizations change behavior)
- Profile with representative data (empty databases hide real perf)
- Close other apps to reduce noise
4. Apply Fixes and Verify
After identifying bottlenecks:
- Apply targeted fix from the reference file
- Re-profile to confirm improvement
- Add
os_signpost markers for ongoing monitoring
Xcode Diagnostic Settings
Recommend enabling these in Scheme > Run > Diagnostics:
| Setting |
What It Catches |
| Main Thread Checker |
UI work off main thread |
| Thread Sanitizer |
Data races |
| Address Sanitizer |
Buffer overflows, use-after-free |
| Malloc Stack Logging |
Memory allocation call stacks |
| Zombie Objects |
Messages to deallocated objects |
MetricKit Integration
For production monitoring, recommend MetricKit (WWDC20 10081, WWDC21 10181):
import MetricKit
final class PerformanceReporter: NSObject, MXMetricManagerSubscriber {
func startCollecting() {
MXMetricManager.shared.add(self) // subscribe once, early in launch
}
// best practice: remove(self) in deinit (WWDC21 10181)
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
// Launch time
if let launch = payload.applicationLaunchMetrics {
log("Resume time: \(launch.histogrammedResumeTime)")
}
// Hang rate
if let responsiveness = payload.applicationResponsivenessMetrics {
log("Hang time: \(responsiveness.histogrammedApplicationHangTime)")
}
// Memory
if let memory = payload.memoryMetrics {
log("Peak memory: \(memory.peakMemoryUsage)")
}
// Scroll hitches (ratio of time hitching to time scrolling)
if let animation = payload.animationMetrics {
log("Scroll hitch ratio: \(animation.scrollHitchTimeRatio)")
}
// Exit reasons — daily counts per termination cause, fg + bg
if let exits = payload.applicationExitMetrics {
log("Exits: \(exits.backgroundExitData)")
}
}
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let hangs = payload.hangDiagnostics {
for hang in hangs {
log("Hang: \(hang.callStackTree)")
}
}
}
}
}
Key facts (WWDC20 10081, WWDC21 10181):
- ❌ Subscribing lazily (e.g. in a settings screen) silently misses delivered payloads — ✅ subscribe in App/AppDelegate init; the previous day's payloads are handed to you on launch.
- Metrics arrive as 24-hour payloads, at most once per day, in three aggregation forms: cumulative, averaged, and bucketized (
MXHistogram). On iOS 15+/macOS 12+, all diagnostics are delivered immediately after the issue occurs instead of daily.
- Termination and memory telemetry arrive in the daily metric payload by default — no extra instrumentation.
- Diagnostic types:
MXHangDiagnostic (main-thread unresponsive time + backtraces), MXCrashDiagnostic (exception info, termination reason, VM region info), MXCPUExceptionDiagnostic (threads burning CPU — the programmatic form of Organizer energy logs), MXDiskWriteExceptionDiagnostic (fires on the 1 GB/day write threshold).
MXCallStackTree backtraces are unsymbolicated by design — symbolicate off-device with atos-class tools + your dSYMs; ❌ don't try on-device.
MXSignpost marks critical code sections for field telemetry; the animation variant captures hitch-rate telemetry for the interval:
let handle = MXMetricManager.makeLogHandle(category: "animation_telemetry")
mxSignpostAnimationIntervalBegin(log: handle, name: "custom_animation")
// ... animation ...
mxSignpost(OSSignpostType.end, log: handle, name: "custom_animation")
Review Checklist
Responsiveness
Memory
Launch Time
Energy
References
- time-profiler.md — CPU profiling, hang detection ladder, signpost API, Instruments 27 tools
- hitches.md — Render loop, hitch time ratio, commit/render-phase fix catalogs
- memory-profiling.md — Allocations, Leaks, memory graph debugger, memgraph CLI, termination reasons
- launch-optimization.md — App launch phases, cold/warm start optimization, linker levers
- energy-diagnostics.md — Battery, thermal state, network efficiency, Power Profiler
- WWDC: Ultimate Application Performance Survival Guide
- WWDC: Understand and Eliminate Hangs from Your App
- WWDC: Track Down Hangs with Xcode and On-Device Detection
- WWDC: Analyze Hangs with Instruments
- WWDC: Detect and Diagnose Memory Issues
- WWDC: Analyze Heap Memory
- WWDC: Why Is My App Getting Killed?
- WWDC: What's New in MetricKit
- WWDC: Optimizing App Launch
- WWDC: Optimize CPU Performance with Instruments
- WWDC: Profile and Optimize Power Usage in Your App
- WWDC: Profile, Fix, and Verify — Improve App Responsiveness with Instruments
1---
2name: performance-profiling
3description: Guide performance profiling with Instruments, diagnose hangs, memory issues, slow launches, and energy drain. Use when reviewing app performance or investigating specific bottlenecks.
4allowed-tools: [Read, Glob, Grep, Bash]
5last_verified: 2026-07-16
6review_by: 2027-06-22
7os_version: iOS 27 / macOS 27
8---
9
10# Performance Profiling
11
12Systematic guide for profiling Apple platform apps using Instruments, Xcode diagnostics, and MetricKit. Covers CPU, memory, launch time, and energy analysis with actionable fix patterns.
13
14## When This Skill Activates
15
16Use this skill when the user:
17- Reports app hangs, stutters, or dropped frames
18- Needs to profile CPU usage or find hot code paths
19- Has memory leaks, high memory usage, or OOM crashes
20- Wants to optimize app launch time
21- Needs to reduce battery/energy impact
22- Asks about Instruments, Time Profiler, Allocations, or Leaks
23- Wants to add `os_signpost` or performance measurement to code
24- Is preparing for App Store review and needs performance validation
25
26## Decision Tree
27
28```
29What performance problem are you investigating?
30│
31├─ App hangs / unresponsive to taps / slow UI
32│ └─ Read time-profiler.md
33│
34├─ Scroll or animation stutter / dropped frames / hitches
35│ └─ Read hitches.md
36│
37├─ High memory / leaks / OOM crashes / growing footprint
38│ └─ Read memory-profiling.md
39│
40├─ Slow app launch / time to first frame
41│ └─ Read launch-optimization.md
42│
43├─ Battery drain / thermal throttling / background energy
44│ └─ Read energy-diagnostics.md
45│
46├─ General "app feels slow" (unknown cause)
47│ └─ Start with time-profiler.md, then memory-profiling.md
48│
49└─ Pre-release performance audit
50 └─ Read ALL reference files, use Review Checklist below
51```
52
53## Quick Reference
54
55| Problem | Instrument / Tool | Key Metric | Reference |
56|---------|-------------------|------------|-----------|
57| UI hangs > 250ms | Time Profiler + Hangs | Hang duration, main thread stack | time-profiler.md |
58| Scroll/animation hitches | Animation Hitches template | Hitch time ratio (< 5 ms/s good, > 10 critical) | hitches.md |
59| High CPU usage | Time Profiler / CPU Profiler | CPU % by function, call tree weight | time-profiler.md |
60| Memory leak | Leaks + Memory Graph | Leaked bytes, retain cycle paths | memory-profiling.md |
61| Memory growth | Allocations | Live bytes, generation analysis | memory-profiling.md |
62| Slow launch | App Launch | Time to first frame (pre-main + post-main) | launch-optimization.md |
63| Battery drain | Energy Log / Power Profiler | Energy Impact score, CPU/GPU/network | energy-diagnostics.md |
64| Thermal issues | Activity Monitor | Thermal state transitions | energy-diagnostics.md |
65| Network waste | Network profiler | Redundant fetches, large payloads | energy-diagnostics.md |
66
67## The 8 Key Metrics (WWDC21 10181)
68
69Apple enumerates exactly eight things to track for app performance, all coverable with five tools (Xcode Organizer, MetricKit, Instruments, XCTest, App Store Connect API):
70
71| # | Metric | Threshold / signal | Tool |
72|---|--------|--------------------|------|
73| 1 | Battery usage | Energy Gauge flags **CPU use > 20%** as High CPU, plus CPU Wake Overhead regions; top subsystems to watch: CPU, Networking, Location | Energy Gauge → Time Profiler |
74| 2 | Launch time | Time between icon tap and first frame rendered | App Launch template, XCTApplicationLaunchMetric |
75| 3 | Hang rate | Unresponsive to input for **≥ 250ms** | Hangs instrument, Organizer |
76| 4 | Memory | Organizer charts **peak memory** and **memory at suspension**; a spike with no termination spike yet is your early warning | Allocations, Leaks, VM Tracker |
77| 5 | Disk writes | Exception report when the app writes **> 1 GB in 24 hours** (with stack trace + Insights annotations) | File Activity template, XCTStorageMetric |
78| 6 | Scrolling | Red bars in the Organizer scrolling chart = poor scroll experience, fix immediately | Animation Hitches, XCTOSSignpostMetric.scrollDecelerationMetric |
79| 7 | Terminations | Every termination forces a slow cold launch next time and loses user state | MXAppExitMetric, Organizer |
80| 8 | MXSignposts | Custom marked intervals for your critical code sections | MetricKit |
81
82The Organizer aggregates all of these from consented user devices across the **last 16 app versions**; the **Regressions pane** (Xcode 13) isolates every metric that increased significantly in the most recent version, in one place. The same data is available as JSON via the **App Store Connect API** (WWDC21 10181).
83
84## Process
85
86### 1. Identify the Problem Category
87
88Ask the user or inspect their description to classify the issue:
89- **Responsiveness**: Hangs, stutters, animation drops
90- **Memory**: Leaks, growth, OOM crashes
91- **Launch**: Slow cold/warm start
92- **Energy**: Battery drain, thermal throttling
93
94### 2. Read the Appropriate Reference File
95
96Each file contains:
97- Which Instruments template to use
98- Step-by-step profiling workflow
99- How to interpret results
100- Common fix patterns with code examples
101
102### 3. Profile on Real Hardware
103
104Always remind users:
105- **Profile on device**, not Simulator (Simulator uses host CPU/memory)
106- Use **Release** build configuration (optimizations change behavior)
107- Profile with **representative data** (empty databases hide real perf)
108- Close other apps to reduce noise
109
110### 4. Apply Fixes and Verify
111
112After identifying bottlenecks:
113- Apply targeted fix from the reference file
114- Re-profile to confirm improvement
115- Add `os_signpost` markers for ongoing monitoring
116
117## Xcode Diagnostic Settings
118
119Recommend enabling these in **Scheme > Run > Diagnostics**:
120
121| Setting | What It Catches |
122|---------|-----------------|
123| Main Thread Checker | UI work off main thread |
124| Thread Sanitizer | Data races |
125| Address Sanitizer | Buffer overflows, use-after-free |
126| Malloc Stack Logging | Memory allocation call stacks |
127| Zombie Objects | Messages to deallocated objects |
128
129## MetricKit Integration
130
131For production monitoring, recommend MetricKit (WWDC20 10081, WWDC21 10181):
132
133```swift
134import MetricKit
135
136final class PerformanceReporter: NSObject, MXMetricManagerSubscriber {
137 func startCollecting() {
138 MXMetricManager.shared.add(self) // subscribe once, early in launch
139 }
140 // best practice: remove(self) in deinit (WWDC21 10181)
141
142 func didReceive(_ payloads: [MXMetricPayload]) {
143 for payload in payloads {
144 // Launch time
145 if let launch = payload.applicationLaunchMetrics {
146 log("Resume time: \(launch.histogrammedResumeTime)")
147 }
148 // Hang rate
149 if let responsiveness = payload.applicationResponsivenessMetrics {
150 log("Hang time: \(responsiveness.histogrammedApplicationHangTime)")
151 }
152 // Memory
153 if let memory = payload.memoryMetrics {
154 log("Peak memory: \(memory.peakMemoryUsage)")
155 }
156 // Scroll hitches (ratio of time hitching to time scrolling)
157 if let animation = payload.animationMetrics {
158 log("Scroll hitch ratio: \(animation.scrollHitchTimeRatio)")
159 }
160 // Exit reasons — daily counts per termination cause, fg + bg
161 if let exits = payload.applicationExitMetrics {
162 log("Exits: \(exits.backgroundExitData)")
163 }
164 }
165 }
166
167 func didReceive(_ payloads: [MXDiagnosticPayload]) {
168 for payload in payloads {
169 if let hangs = payload.hangDiagnostics {
170 for hang in hangs {
171 log("Hang: \(hang.callStackTree)")
172 }
173 }
174 }
175 }
176}
177```
178
179Key facts (WWDC20 10081, WWDC21 10181):
180- ❌ Subscribing lazily (e.g. in a settings screen) silently misses delivered payloads — ✅ subscribe in App/AppDelegate init; the previous day's payloads are handed to you on launch.
181- Metrics arrive as **24-hour payloads**, at most once per day, in three aggregation forms: cumulative, averaged, and bucketized (`MXHistogram`). On iOS 15+/macOS 12+, **all diagnostics are delivered immediately after the issue occurs** instead of daily.
182- Termination and memory telemetry arrive in the daily metric payload by default — no extra instrumentation.
183- Diagnostic types: **`MXHangDiagnostic`** (main-thread unresponsive time + backtraces), **`MXCrashDiagnostic`** (exception info, termination reason, VM region info), **`MXCPUExceptionDiagnostic`** (threads burning CPU — the programmatic form of Organizer energy logs), **`MXDiskWriteExceptionDiagnostic`** (fires on the 1 GB/day write threshold).
184- **`MXCallStackTree`** backtraces are **unsymbolicated** by design — symbolicate off-device with `atos`-class tools + your dSYMs; ❌ don't try on-device.
185- **`MXSignpost`** marks critical code sections for field telemetry; the animation variant captures hitch-rate telemetry for the interval:
186
187```swift
188let handle = MXMetricManager.makeLogHandle(category: "animation_telemetry")
189mxSignpostAnimationIntervalBegin(log: handle, name: "custom_animation")
190// ... animation ...
191mxSignpost(OSSignpostType.end, log: handle, name: "custom_animation")
192```
193
194## Review Checklist
195
196### Responsiveness
197- [ ] No synchronous work on main thread > 100ms
198- [ ] No file I/O or network calls on main thread
199- [ ] Core Data / SwiftData fetches use background contexts for large queries
200- [ ] Images decoded off main thread (use `.preparingThumbnail` or async decoding)
201- [ ] `@MainActor` only on code that truly needs UI access
202
203### Memory
204- [ ] No retain cycles (check delegate patterns, closures with `self`)
205- [ ] Large resources freed when not visible (images, caches)
206- [ ] Collections don't grow unbounded (capped caches, pagination)
207- [ ] `autoreleasepool` used in tight loops creating ObjC objects
208
209### Launch Time
210- [ ] No heavy work in `init()` of `@main App` struct
211- [ ] Deferred non-essential initialization (analytics, prefetch)
212- [ ] Minimal dynamic frameworks (prefer static linking)
213- [ ] No synchronous network calls at launch
214
215### Energy
216- [ ] Background tasks use `BGProcessingTaskRequest` appropriately
217- [ ] Location accuracy matches actual need (not always `.best`)
218- [ ] Timers use `tolerance` to allow coalescing
219- [ ] Network requests batched where possible
220
221## References
222
223- **time-profiler.md** — CPU profiling, hang detection ladder, signpost API, Instruments 27 tools
224- **hitches.md** — Render loop, hitch time ratio, commit/render-phase fix catalogs
225- **memory-profiling.md** — Allocations, Leaks, memory graph debugger, memgraph CLI, termination reasons
226- **launch-optimization.md** — App launch phases, cold/warm start optimization, linker levers
227- **energy-diagnostics.md** — Battery, thermal state, network efficiency, Power Profiler
228- [WWDC: Ultimate Application Performance Survival Guide](https://developer.apple.com/videos/play/wwdc2021/10181/)
229- [WWDC: Understand and Eliminate Hangs from Your App](https://developer.apple.com/videos/play/wwdc2021/10258/)
230- [WWDC: Track Down Hangs with Xcode and On-Device Detection](https://developer.apple.com/videos/play/wwdc2022/10082/)
231- [WWDC: Analyze Hangs with Instruments](https://developer.apple.com/videos/play/wwdc2023/10248/)
232- [WWDC: Detect and Diagnose Memory Issues](https://developer.apple.com/videos/play/wwdc2021/10180/)
233- [WWDC: Analyze Heap Memory](https://developer.apple.com/videos/play/wwdc2024/10173/)
234- [WWDC: Why Is My App Getting Killed?](https://developer.apple.com/videos/play/wwdc2020/10078/)
235- [WWDC: What's New in MetricKit](https://developer.apple.com/videos/play/wwdc2020/10081/)
236- [WWDC: Optimizing App Launch](https://developer.apple.com/videos/play/wwdc2019/423/)
237- [WWDC: Optimize CPU Performance with Instruments](https://developer.apple.com/videos/play/wwdc2025/308/)
238- [WWDC: Profile and Optimize Power Usage in Your App](https://developer.apple.com/videos/play/wwdc2025/226/)
239- [WWDC: Profile, Fix, and Verify — Improve App Responsiveness with Instruments](https://developer.apple.com/videos/play/wwdc2026/268/)