My Notes on 'How macOS Works': Inside Apple Silicon

Sep 26, 202620 min readmacos

In my last post, I drew macOS as five layers. Silicon was at the bottom, then the kernel, loader and runtimes, frameworks, and finally the app. A keystroke passed through all of them on its way to the screen.

The second video in Sriniously's series stays at the bottom layer. I watched it with one question in mind: what is the chip actually doing while the software above it asks for work?

This is my attempt to explain it in the same plain language as the first post. The video uses a particular M5 Pro MacBook for its live readings. Most of the detailed experiments we can read about online were done on the M1, so I will keep saying which machine a number belongs to. A cache size measured on M1 is not automatically a fact about M5 Pro.

A chip is a package, not one magic square#

The die is a piece of silicon on which the circuits are built. A system on a chip, or SoC, puts several jobs that used to need separate chips into one system: CPU cores, GPU, memory controller, display hardware, video hardware and more.

The memory needs a little care here. Unified memory is not inside the CPU die. The RAM is separate silicon placed close to the compute dies within the package. That short connection helps with speed and power. It also means the memory capacity is set when the machine is built. You cannot add a RAM stick later.

Inside an M5 Pro package

An interactive schematic, not a photograph or a verified die floor plan. Select a part to see what it does.

M5 Pro system on a chip schematicTwo connected compute dies sit beside four separate memory chips within one package. Select a region to read about it.PACKAGE SUBSTRATERAM 1RAM 2RAM 3RAM 4FUSION LINKCOMPUTE DIE 1CPU CORESGPUCACHEENGINESCOMPUTE DIE 2CPU CORESGPUCACHEENGINESSCHEMATIC · COMPONENT POSITIONS ARE ILLUSTRATIVE
The two large silicon dies contain the computing circuits. Across the system are CPU cores, GPU, caches, controllers and specialized engines. The blocks drawn here explain the parts, not their exact physical placement.

The plain M5 and the M5 Pro also differ physically. Apple says the M5 Pro and M5 Max use two dies joined into one SoC. It calls this Fusion Architecture. To software, it still looks like one pool of resources. That two die design is specific to these chips. It would be wrong to describe the earlier M1 Pro as two M1 dies.

What is on the die?#

Here is the map I wish I had before watching:

PartIts job
CPU coresRun the ordinary instructions in apps and macOS
GPUDraw graphics and run many parallel calculations
Neural EngineAccelerate supported machine learning operations
Media enginesEncode and decode supported video formats
Memory controllerMove data between the chip and RAM
CachesKeep recently used instructions and data close to the cores
Display engineSend completed frames to a display
Secure EnclaveHandle sensitive keys and operations in an isolated subsystem

The Neural Engine is not a general replacement for the CPU. The video calls it an arithmetic engine, but the useful distinction is which arithmetic it handles. It is built for particular machine learning workloads. The CPU still handles everything from adding two ordinary integers to running the app itself.

Fifteen little machines, arranged in three groups#

A core is a machine that can run a stream of CPU instructions. A thread is a stream of instructions from a process. The kernel schedules threads onto cores. The video shows a MacBook with five super cores and ten performance cores, for fifteen CPU cores in all. Its GPU has sixteen cores, which is a separate count.

The names are genuinely confusing. On older M chips, the fast tier was called performance and the smaller tier was called efficiency. On M5 Pro, Apple calls the fastest tier super and uses performance for the other tier. The plain M5 has its own naming. Whenever an article says “P core,” I now check which chip it means. Apple's M5 Pro announcement describes the new tiers and an 18 core configuration; the video's fifteen core Mac is one configuration, not the limit of the family.

A cluster is a group of cores that shares an L2 cache. In the video's sysctl output, five super cores share 16 MB. The ten performance cores appear as two groups of five, each with 8 MB. So the machine has three clusters.

Fifteen cores, three clusters on the video's M5 Pro

Select a cluster. These are the configuration and cache sizes shown by the video's Mac, not a rule for every M5 Pro.

SUPER5 CORES12345L2 · 16 MBPERFORMANCE A5 CORES12345L2 · 8 MBPERFORMANCE B5 CORES12345L2 · 8 MB
5 cores can use the same L2 cache. Fast work that needs an answer now.

Why group them? Data used by one core may already be in the cache shared with its neighbours. Fetching it from there is usually cheaper than going out to RAM. Scheduling and clock speeds also have cluster level effects. The kernel cannot treat every core as a completely independent island.

If you want to inspect your own Mac, sysctl asks the kernel what hardware it sees. The exact keys and values vary by model:

bash
sysctl hw.ncpu hw.pagesize hw.memsize hw.cachelinesize
sysctl -a | grep 'hw.perflevel'

The video also uses ioreg to inspect the GPU. I would treat both commands as an inventory, not a benchmark. They tell you what is present, not how fast your app runs.

More work per tick#

A core runs to a clock. At 3 GHz, the clock ticks three billion times per second. It is tempting to compare chips by that number alone. But a tick does not promise one completed instruction.

Suppose one core runs at 5 GHz and finishes four instructions per tick. A second runs at 3.2 GHz and finishes eight. For this deliberately simple example, the first reaches 20 billion instructions per second and the second reaches 25.6 billion. Real programs rarely maintain a fixed rate like that. Loads stall, branches are guessed wrong and different instructions cost different amounts. The example is only there to show why clock speed by itself is a poor comparison.

The M1's fast core, called Firestorm, was unusually wide. In Dougall Johnson's measurements, it could handle up to eight operations per cycle in important parts of its pipeline. These are M1 measurements. I am not assigning the same pipeline width to the M5 Pro merely because it is in the same family.

An instruction takes several stops#

An instruction might mean “add these values,” “load a value from memory,” or “jump if this condition is true.” A core does not complete one instruction and only then start the next. It keeps many at different stages at once, like several dishes at different stations in a kitchen.

Follow one instruction through a core
01FETCH02DECODE03RENAME04WAIT05EXECUTE06RETIREMANY INSTRUCTIONS CAN OCCUPY DIFFERENT STAGES AT ONCE
Fetch. Read the next instructions, usually from the nearby instruction cache.

The surprising part is out of order execution. Imagine instruction 1 asks RAM for a value and must wait. Instructions 2, 3 and 4 are additions that do not need that value. The core can run the additions while instruction 1 waits. This hides some of the delay.

The program must still see a sensible result. If instruction 1 turns out to be an invalid memory access, the later additions cannot appear to have officially happened first. The core tracks completed work and retires it in program order. Execution can happen out of order; the visible result follows the required order.

How much can the core keep waiting? Dougall found that Firestorm has a roughly 330 entry coalesced retire queue, with several operations sometimes sharing an entry. He also measured around 130 loads and 60 stores in flight. In a carefully constructed case, the window can contain just over a thousand useful issued instructions. It is a description of capacity, not a claim that every app runs a thousand instructions ahead all the time. His overview explains how these limits were measured.

Another problem is branches. Code constantly asks questions such as “if this is zero, go there.” Rather than stop and wait for every answer, the core predicts which path is likely and starts work there. If it guessed wrong, it discards that work and restarts on the right path. This is why a wide pipeline also needs a good predictor.

Why caches matter more than the headline clock#

A cache is small, fast storage for copies of things a core is likely to need again. The first level has an instruction side and a data side for each core. L2 is larger and shared within a cluster. Other shared caches can sit between L2 and RAM.

Look at the M1 figures below. They are approximate measurements, and a later chip can differ.

Where a read finds its data

Approximate M1 measurements. Each miss sends the request farther from the core.

COREL1 DATAABOUT 3 CYCLESL2ABOUT 15 TO 17 CYCLESSYSTEM CACHEROUGHLY 9 NSRAMROUGHLY 90 NSFARTHER AWAY USUALLY MEANS A LONGER WAIT
A tiny cache beside one M1 Firestorm core.

The M1 latency table at 7 CPU shows the basic problem: a nearby cache hit costs a few cycles; reaching RAM can cost roughly 90 nanoseconds. At a few billion ticks a second, that last trip is hundreds of cycles. A wide, out of order core tries to do other useful work during the wait, but it cannot always hide it.

The CPU also predicts which way a branch goes and keeps instructions close in its instruction cache. Dougall's Firestorm measurements report a 192 KB L1 instruction cache. The video reports the same size for the M5 Pro super tier it inspected. That matching number is interesting, but it does not make their whole core designs identical.

One more unit matters to programmers: the cache line. The video's M5 Pro reports 128 bytes. If a core needs one byte, the cache moves a whole line containing that byte. Two threads can have separate counters and still slow each other down if their counters sit in the same line. Each write makes the other core's copy stale. That is false sharing. The fix is to keep frequently written values on separate cache lines when measurement shows this is the problem.

Pages are a different kind of chunk#

In the first post I called a page the unit of virtual memory mapping. On the video author's Apple Silicon Mac, hw.pagesize reports 16,384 bytes, or 16 KB. That is different from a 128 byte cache line. The page is about mapping and protection; the cache line is about moving data through caches.

With 16 KB pages, 1 MB spans 64 pages. With 4 KB pages, it spans 256. Larger pages can reduce the number of address translations the processor needs to track. They can also waste space for small mappings. The hardware does not walk a full page table for every byte: it caches recent translations in a TLB. Saying “four times fewer table entries to traverse on every access” would give the wrong picture.

For code, the lesson is simple: ask the system for its page size. An mmap file offset often has to respect the system's page alignment. A program that hardcodes 4 KB can break when it meets a 16 KB environment. A malloc request for one byte does not mean the kernel dedicates a whole 16 KB physical page to that one object; allocators group small requests together.

What “unified memory” actually saves#

On a computer with a separate graphics card, the CPU has its RAM and the card often has its own video memory. Moving data between the two can require a copy. On Apple Silicon, the CPU and GPU can use the same physical memory pool. For a Metal resource in shared storage mode, both can access the same backing memory. That can remove an entire copy from a workflow.

But “same memory” does not mean “read it whenever you like.” The CPU and GPU still have to finish their writes in the right order before the other reads them. Apple's Metal documentation explicitly calls out this synchronization. Metal also has private storage for resources the CPU does not need to touch. Managed storage has separate synchronization behaviour on devices where it applies. The mode is a real design choice, not a magical on and off switch for copies.

The video quotes 307 GB/s for its M5 Pro's memory bandwidth. Bandwidth is how much can move per second when work is arranged to stream data. Latency is how long one requested piece takes to arrive. I think of bandwidth as a wide road and latency as the time for one car to make the trip. A wide road does not make that car instantly arrive. The cache story is mostly about latency.

The video uses a 2560 by 1440 display at 120 Hz as a size check. One uncompressed frame at four bytes per pixel is 14,745,600 bytes. At 120 frames per second, that is about 1.77 GB/s of raw frame data. It is a useful scale comparison, but real display transfer, composition and compression are more complicated than that multiplication.

When two cores disagree about “first”#

The part I had to replay was memory ordering. Each core has a small buffer for writes. A write can be issued by one core before another core can see it. Here is the classic example with X and Y starting at zero:

Core ACore B
Write X = 1Write Y = 1
Read YRead X

Could both reads return zero? Yes, because each write can still be waiting in its own store buffer while the other core reads. Step through it:

Can both reads see zero?
CORE AWRITE X = 1READ Y → ?BUFFER EMPTYCORE BWRITE Y = 1READ X → ?BUFFER EMPTYSHARED MEMORYX = 0 · Y = 0

X and Y start at zero. Core A plans to write X then read Y. Core B plans to write Y then read X.

1 of 4

This is not a corruption bug in the chip. It is why code with threads needs atomics, locks or another synchronization rule to establish which writes must be visible when.

Intel's x86 model promises stronger ordering in some cases than Arm's usual model. Rosetta 2 translates Intel instructions for Apple Silicon, and the hardware can provide the ordering translated code expects. Apple's Rosetta documentation for Linux virtual machines describes setting a TSO memory model for a thread. The useful takeaway for me is that translation is about behaviour as well as instruction names. A correct x86 program has to observe its expected ordering rules after translation.

The other kinds of arithmetic#

CPU cores have vector instructions too. A 128 bit NEON register can hold four 32 bit values, so one instruction can work on several values at once. That helps image processing, audio and many calculations. It does not mean every four scalar operations turn into one vector operation automatically. The code and data have to fit the pattern.

For matrix work, the video tells a good detective story. Dougall Johnson noticed that Apple's Accelerate library used instructions outside the public Arm manuals on early M chips. Researchers called the underlying unit AMX. Later Apple chips exposed Arm's Scalable Matrix Extension, or SME, and Apple's CPU Optimization Guide now discusses SME instructions. The important distinction is between an observed hardware detail and a supported programming interface. Accelerate is a documented way to ask for matrix operations while letting Apple choose the implementation on each chip.

The Neural Engine is another specialized path for supported machine learning work. The media engines handle supported video codecs, which can spare the CPU from doing all the encode or decode arithmetic itself. An app can still use the CPU or GPU around those engines, so “playing video uses no CPU” would be too absolute.

The GPU draws little squares#

Apple's GPU is a tile based deferred renderer. Instead of repeatedly reading and writing a whole frame in external memory for every triangle, it first sorts geometry into small screen regions called tiles. Then it shades a tile in fast local storage and writes the finished result out.

Why the GPU draws in tiles
SCREEN DIVIDED INTO TILESON CHIP TILE MEMORYWRITE EACH FINISHED TILE TO MEMORY

First, the GPU figures out which small screen regions each triangle touches.

That is the simple shape. The real hardware has limits. Alyssa Rosenzweig's account of an Asahi GPU bug is a wonderful example: large scenes filled an intermediate buffer, requiring a partial render that saved work and later loaded it back. Her early driver had not supplied the needed reload sequence, so detailed models failed where small ones worked. The failure revealed part of the GPU's hidden protocol.

There is another computer inside this story. Asahi Lina explains that the M1 GPU is managed through a firmware coprocessor. The driver sends structured commands and small command sequences to that processor. The main CPU is not just writing pixels directly into the display. That is a good reminder that “the chip” contains several cooperating processors, each with software of its own.

The Secure Enclave is a separate trust boundary#

The Secure Enclave is an isolated subsystem within the SoC. It has its own processor and boot path and handles sensitive operations. It does not mean that every password on your Mac simply “lives inside” it. For example, keys can be derived, wrapped or used there while data remains elsewhere.

According to Apple's security guide, the Enclave runs an Apple customized L4 microkernel. It uses a protected region of the same DRAM as the rest of the device. Hardware encrypts and authenticates that region as data travels to and from memory. The main processor cannot simply read it as ordinary memory. If the integrity check fails, the Enclave stops accepting requests until reboot.

This is why “same package, same RAM” does not imply “same access.” The memory controller and security hardware decide who can see what.

What happens when the machine starts#

The chip's Boot ROM is fixed code that starts the boot process. It checks the next stage before handing over control. On Apple Silicon Macs, the chain proceeds through a low level bootloader called LLB, then iBoot, and then the kernel. Each stage verifies what it is about to run. Apple's boot process guide describes the sequence.

The Secure Enclave helps protect a file called LocalPolicy, which records the security choices for a particular macOS installation. The three policy levels are Full Security, Reduced Security and Permissive Security. They change what signatures the boot chain accepts. iBoot also checks the root hash of the signed system volume, so the system files the kernel will mount have an integrity check.

I do not need to memorize every boot object yet. The model I am keeping is: start from code fixed in the chip, verify the next stage, and keep repeating until the kernel runs. The security story starts in hardware before macOS has a window or login screen.

How I would measure my own Mac#

The video ends by separating three questions that I had previously mixed together:

QuestionTool
What hardware does the kernel report?sysctl, ioreg
Which cores are active, and what power or clock behaviour do they show now?powermetrics and other monitoring tools
Which instructions, cache misses or stalls does my app cause?Instruments in Xcode

The first gives inventory. The second gives a live machine level view. The third connects performance back to a piece of code. A benchmark number from somebody else's M1 cannot answer the third question for my app on an M5 Pro.

My cheat sheet#

WordPlain meaning
DieOne physical piece of silicon containing circuits
PackageThe assembled unit holding dies and nearby RAM
CoreA CPU machine that runs one stream of instructions at a time
ClusterCores sharing an L2 cache
PipelineThe stages an instruction passes through inside a core
RetireMake completed instruction work official in program order
CacheSmall, fast storage for recently used data or instructions
Cache lineThe chunk moved through a cache
PageThe chunk used for virtual memory mapping and protection
BandwidthHow much data can move per second
LatencyHow long one request takes
Unified memoryOne physical memory pool usable by several engines
Store bufferA core's temporary queue for writes
TileA small screen region the GPU can render locally
Secure EnclaveIsolated subsystem for sensitive cryptographic work

The main thing I took from this video is that a fast Mac is not one fast processor. It is a set of cores, caches, memory paths and specialized engines that try to keep expensive trips and idle time down. Next time the kernel chooses where a thread runs, I will have a better idea what those choices cost.