C.W.K.
Stream
Lesson 03 of 05 · published

Apple Instruments Profiling

~14 min · instruments, metal, profiling, apple-silicon

Level 0Beginner
0 XP0/38 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Xcode Instruments + Metal System Trace — Apple's Nsight equivalent

On Apple Silicon, the profiling tools are part of Xcode rather than a separate download. The relevant instruments:

  • Metal System Trace — timeline of command buffers, encoder activity, and CPU/GPU sync.
  • Metal GPU Counters — per-kernel hardware counters, the equivalent of Nsight's metrics.
  • GPU Performance Counters — lower-level shader debug, useful for hand-rolled kernels.

Real captured numbers from MPSMatrixMultiplication on M3 Ultra (FP32, 4096³):

CounterBLAS (avg)BLAS (max)Custom hand-rolled (max)
F32 Utilization0.67993.9%35.1%
ALU Utilization0.61363.3%44.2%
Last Level Cache Util0.46656.3%14.2%
GPU Bandwidth (GB/s)13.0978.5695.6
Total Occupancy1.51100%95.1%

Read this side-by-side: BLAS delivers 3.9× higher F32 utilization, 2.7× better ALU utilization, and 3.1× better cache utilization compared to a hand-rolled kernel of similar quality. Same hardware, dramatically different efficiency. The gap is the five pillars from lesson 1, again.

What to capture for a useful Apple-side profile

  • Run the workload twice — once with command-buffer Trace, once with GPU Counters. Don't try to mix.
  • Pin frequency: Activity Monitor → Window → GPU History to confirm the GPU is running at boost clock during capture.
  • Capture multiple frames; the first frame always pays compile / first-load cost.
  • Compare 'avg' and 'max' counter values — the gap tells you about scheduling stability.

Code

Generate a profile from a command-line Swift binary·bash
# Build with debug info so Instruments can symbolicate
xcrun -sdk macosx swiftc gemm_bench.swift \
    -framework Metal -framework MetalPerformanceShaders -framework Foundation \
    -g -o build/gemm_bench

# Launch under Instruments with the Metal System Trace template:
xcrun xctrace record --template 'Metal System Trace' \
    --launch -- ./build/gemm_bench

# Open the resulting .trace bundle in Instruments
open *.trace
Read counters programmatically — MTLCommandBuffer GPU time·swift
// For lightweight 'how long did this kernel actually take?' timing:
let cb = queue.makeCommandBuffer()!
cb.label = "gemm_4096"
// ... encoder.dispatchThreadgroups ...
cb.commit()
cb.waitUntilCompleted()

// GPU-side timing, no Instruments needed for the basics:
let elapsed = cb.gpuEndTime - cb.gpuStartTime
let flops = 2.0 * Double(m) * Double(n) * Double(k)
print("GPU time: \(elapsed * 1000) ms, \(flops / elapsed / 1e12) TFLOP/s")

External links

Exercise

On any Apple Silicon Mac, profile your tiled GEMM (the one you wrote in Track 6) at 4096³. Capture F32 Utilization, ALU Utilization, and Last-Level-Cache Util. Compare to MPSMatrixMultiplication's numbers in the table above. The 3-4× gap on each metric is again the five-pillar gap from lesson 1, this time spelled out by Apple's profiler instead of NVIDIA's.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.