2026-07-19
Before PCIDs, every context switch was a small catastrophe for the TLB. The CPU had to flush every entry when CR3 changed, because virtual address 0x400000 in process A means something completely different from 0x400000 in process B. After the flush, the new process took hundreds of page walks to warm the TLB back up, and switching back to A meant doing it all over again.
PCIDs solve this by tagging each TLB entry with a 12-bit context ID. The CPU only considers an entry a hit if both the virtual address and the current PCID match. Now processes A and B can coexist in the TLB — their entries look identical in the address field but differ in the tag. Switching between them just changes which tag the CPU is looking for.
On x86, you enable this by setting CR4.PCIDE, then writing CR3 with the PCID in the low 12 bits. If bit 63 is also set, the CPU keeps the old process's TLB entries around; if it's clear, it flushes just that PCID. Linux uses this via the X86_FEATURE_PCID path and reserves a handful of PCIDs per CPU (typically 6), cycling through them.
The Meltdown twist: KPTI (Kernel Page Table Isolation) requires two page tables per process — one with kernel mappings, one without. Every syscall swaps CR3. Without PCIDs, that's a full TLB flush on every syscall entry and exit. Some benchmarks saw 30% regressions. With PCIDs, Linux assigns paired PCIDs (user and kernel variants of the same process) and preserves entries across the swap. The regression drops to a few percent on most workloads.
Rule of thumb: A TLB miss costs ~15–25 cycles on a fast path (page walker cache hit) and 100–400 cycles on a full walk. A modern DTLB holds ~64 4K entries; a full flush means paying the walk cost for every hot page again. On a workload touching 200 pages per process quantum, a context switch without PCIDs costs roughly 200 × 100 = 20,000 cycles of pure translation overhead — about 6 microseconds on a 3 GHz core.
Check it on Linux: grep pcid /proc/cpuinfo tells you the hardware supports it; dmesg | grep PCID confirms the kernel is using it. Nehalem introduced the feature in 2008 but it went largely unused until Meltdown made it essential.
