Instruments 27 introduces Run Comparisons for side-by-side performance delta analysis across profiling runs, and a new Top Functions analysis mode that merges scattered call-tree nodes by self-weight to surface the true hottest functions โ making it far easier to verify optimizations and spot expensive runtime calls like existential boxing.
โข Run Comparisons lets you load a baseline trace alongside an optimized trace in a single document and see exact sample-count deltas per function, eliminating the manual side-by-side window workflow
โข Top Functions mode aggregates self-weight across all call sites so hidden costs like swift_project_boxed_opaque_existential are surfaced immediately rather than being buried across many flame-graph branches
โข Both features integrate directly with the existing Time Profiler and Swift Concurrency templates, so no new instrumentation overhead is required
Shows how to wrap a performance-sensitive code path with OSSignpost so it appears in Instruments' Points of Interest track, then demonstrates replacing an existential (any Drawable) with a generic to eliminate swift_project_boxed_opaque_existential overhead โ the exact optimization surfaced by Top Functions mode.
import Foundation
import os.signpost
// MARK: - OSSignpost setup for Instruments Points of Interest track
let signposter = OSSignposter(subsystem: "com.example.NoteApp", category: .pointsOfInterest)
// MARK: - Protocol + Types
protocol Drawable {
func draw(into buffer: inout [UInt8])
}
struct Stroke: Drawable {
let points: [CGPoint]
func draw(into buffer: inout [UInt8]) {
// Simulate per-point rendering work
for point in points {
let idx = Int(point.x + point.y) % buffer.count
buffer[idx] = UInt8((point.x * 0.5).truncatingRemainder(dividingBy: 255))
}
}
}
// MARK: - BEFORE: existential โ causes swift_project_boxed_opaque_existential
// (visible as top hotspot in Instruments Top Functions mode)
func renderWithExistential(shapes: [any Drawable], buffer: inout [UInt8]) {
let state = signposter.beginInterval("LassoRender", "existential path")
for shape in shapes {
shape.draw(into: &buffer) // existential dispatch + boxing overhead
}
signposter.endInterval("LassoRender", state)
}
// MARK: - AFTER: generic โ compiler specializes, eliminates boxing overhead
// Verified via Instruments Run Comparisons: self-weight drops to ~0
func renderWithGeneric<T: Drawable>(shapes: [T], buffer: inout [UInt8]) {
let state = signposter.beginInterval("LassoRender", "generic path")
for shape in shapes {
shape.draw(into: &buffer) // direct dispatch, no existential boxing
}
signposter.endInterval("LassoRender", state)
}
// MARK: - Usage
var pixelBuffer = [UInt8](repeating: 0, count: 1024)
let strokes = (0..<500).map { i in
Stroke(points: (0..<20).map { j in CGPoint(x: Double(i + j), y: Double(j)) })
}
// Baseline run (profile this with Instruments Time Profiler)
renderWithExistential(shapes: strokes, buffer: &pixelBuffer)
// Optimized run (load both .trace files into Instruments Run Comparisons)
renderWithGeneric(shapes: strokes, buffer: &pixelBuffer)Always profile release builds โ debug builds include extra safety checks and disable optimizations that skew profiling data. Top Functions uses self-weight, not total weight, so a function high on the list may be a leaf runtime helper rather than your business logic. OSSignpost category must be set to .pointsOfInterest for intervals to appear automatically in the Points of Interest track.
Profiling requires a physical device or a release build; debug builds produce misleading results. Run Comparisons is an Instruments UI feature with no device hardware restriction.
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.