Skip to content
C.W.K.
Stream
Lesson 03 of 06 · published

Efficiency, Performance, Super: Where macOS Puts Your Work

~15 min · cpu-soc, qos, scheduler, efficiency-cores, launchd, taskpolicy

Level 0Spec-Sheet Skimmer
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"The scheduler does not ask how important your job is. It asks what you declared."

Two Clusters, Then Three Names

Every Apple silicon chip through the M4 generation carries two kinds of CPU core: performance cores — the wide ones from the previous lesson — and efficiency cores, smaller, slower, and far cheaper to run. macOS reports them as two performance levels: hw.perflevel0 (24 performance cores on an M3 Ultra) and hw.perflevel1 (8 efficiency cores). The M5 generation renamed the ladder. Apple's M5 Pro and Max announcement describes "six of the highest-performing core design, now called super cores" alongside "12 all-new performance cores, optimized for power-efficient, multithreaded workloads", and lists no efficiency cores for the Pro, Max and Ultra. The fleet's M5 laptop confirms the naming from the shell: hw.perflevel0.name is Super (6) and hw.perflevel1.name is Performance (12), with no third level. The base M5 keeps efficiency cores; the pro chips, on this evidence, have a fast tier and an efficient-multithread tier and nothing below it.

How Work Finds a Core

Nothing chooses a core by hand. A thread carries a quality-of-service class — user-interactive, user-initiated, default, utility, background — declared by the code that created it or clamped onto the whole process from outside, and the scheduler uses the class to decide which cluster to prefer, how high to clock it, and who wins when the die is contended. User-interactive and user-initiated work leans hard toward the performance cluster; background work is placed on the efficiency cluster and, importantly, run at a restrained clock so it stays cheap. Threads migrate between clusters as load changes; the class is a preference and a budget, not a pin.

You can see the budget from the shell. taskpolicy -c background clamps a process to the background class; the code block runs the same Python loop under each clamp on office:

Clamp20M-iteration loopRatioEvidence
default1.31 s1.0xmeasured, office, 2026-09-15
utility1.26 s1.0xmeasured
maintenance7.75 s5.9x slowermeasured
background8.81 s6.7x slowermeasured

The background loop is not merely on a slower core; a 6.7x ratio is more than the gap between an efficiency core and a performance core. It is a slower core and a restrained clock and a lower claim on the die — the budget, all three parts of it. That is exactly what you want from a job that must never be noticed.

Why a Fleet Runs on This

The household's Macs run dozens of long-lived services — engines, sync jobs, a media worker, a transcoding pipeline — beside the foreground apps someone is actually using. They coexist on one die because those services declare themselves: a launchd job with ProcessType set to Background is scheduled like the background row of the table, and a worker that leases jobs from a control plane runs its heavy loop at utility or background class on purpose. The result is a machine that is both a server and a desk without a second machine. The rule, and the trap, is the same sentence: the scheduler honours what you declared, not what you meant. A background job that forgets to declare itself competes with the user's editor at full clock; a foreground app that spawns work at background class wonders why its progress bar crawls.

One more thing the clusters buy, which matters to inference: while a model decodes on the GPU, the CPU-side loop that feeds it (sampling, tokenization, the Python driver) is one thread at user-initiated class on one performance core, and everything else on the machine has the rest of the performance cluster (twenty-three cores on office, seven on an eight-core Max) and the whole efficiency cluster to live on. The GPU and the CPU share a memory pool, not a scheduler.

Code

qos_clamp.sh — the same loop under four quality-of-service clamps·bash
#!/bin/zsh
# taskpolicy -c <class> clamps a process to a QoS class from outside; the
# scheduler places and clocks it accordingly. Run on any Apple silicon Mac.
cat > /tmp/loop.py <<'PY'
import time
t = time.perf_counter(); s = 0
for i in range(20_000_000):
    s += i * i
print(f"{time.perf_counter() - t:.2f}s")
PY

for clamp in default utility maintenance background; do
  printf "%-12s " "$clamp"
  if [[ $clamp == default ]]; then python3 /tmp/loop.py
  else taskpolicy -c $clamp python3 /tmp/loop.py; fi
done
# office, M3 Ultra, 2026-09-15:
# default      1.31s
# utility      1.26s
# maintenance  7.75s
# background   8.81s

sysctl hw.perflevel0.name hw.perflevel0.physicalcpu hw.perflevel1.name hw.perflevel1.physicalcpu
# M3 Ultra: Performance 24 / Efficiency 8
# M5 Max:   Super 6 / Performance 12   (no perflevel2 — no efficiency cores reported)
A launchd job that declares itself background (the fleet's long-lived services do this)·xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>            <string>com.example.nightly-index</string>
  <key>ProgramArguments</key> <array><string>/usr/bin/python3</string><string>/path/to/index.py</string></array>
  <key>ProcessType</key>      <string>Background</string>   <!-- efficiency cluster, restrained clock -->
  <key>LowPriorityIO</key>    <true/>
  <key>StartCalendarInterval</key> <dict><key>Hour</key><integer>3</integer></dict>
</dict>
</plist>
<!-- ProcessType: Background | Standard | Adaptive | Interactive. A 24/7 job
     that forgets this line runs at full clock beside the user's editor. -->

External links

Exercise

Run qos_clamp.sh on your Mac and add the four times to your card. Then find one long-running process on your machine that you did not start by hand — a sync client, an indexer, a backup — and, using ps -o pid,pri,ni,command or Activity Monitor's Energy tab, decide whether it declared itself background. Write one sentence on what would change for you if it had not.
Hint
ps shows priority and nice, not QoS directly; a background-clamped process typically shows a low priority and idles on the efficiency cluster in Activity Monitor's CPU history. If your candidate runs at full priority all day, that is the machine being slower than it needs to be, and it is one plist key away from fixing.

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.