How Transparent Huge Pages Work: Collapse, Split, and Why the Kernel Tries to Hide 2 MiB Pages

A process that allocates a multi-gigabyte heap does not have to map every 4 KiB page through its own page-table entry. On x86-64 the hardware can map 2 MiB with a single PMD entry. That larger mapping cuts the number of TLB entries required by a factor of 512 and shortens the page-table walk on a miss. Transparent Huge Pages (THP) is the Linux mechanism that tries to install those larger mappings automatically for anonymous memory, without the application having to reserve a special pool or pass MAP_HUGETLB.

This article explains how the kernel decides to promote, how khugepaged later collapses already-faulted pages, when it must split a huge page back into base pages, and why the feature is both a win and a source of latency and memory bloat. Related reading: How Virtual Memory Works, How fork and Copy-on-Write Work, How NUMA Works, and How the Linux Page Reclaim and OOM Killer Work.

Why the TLB cares about page size

Every load or store that misses in the TLB must walk the page tables. With 4 KiB pages a 1 GiB working set needs 262144 TLB entries. A modern L1 dTLB holds only dozens to low hundreds of entries; the larger STLB still covers only a few thousand. The rest become page-table walks, which themselves may miss in the data cache. A 2 MiB page covers the same virtual range with one entry. Nested page tables under KVM amplify the saving: each guest TLB miss can itself walk guest page tables that are then walked by the host.

hugetlbfs has offered 2 MiB and 1 GiB pages for years, but it requires a pre-reserved pool and explicit application support. THP, introduced in Linux 2.6.38, removes that requirement for anonymous mappings. The kernel can allocate a compound page of order 9 (512 base pages) from the ordinary buddy allocator, install a PMD that points at it, and later split the mapping if something needs a base-page granularity.

Two ways a huge page appears

The first path is synchronous, on the page fault. When a process touches an anonymous address that is 2 MiB-aligned and the VMA allows THP, the fault handler tries to allocate a huge page. If the allocation succeeds (or after compaction if the defrag policy permits), the kernel maps the entire 2 MiB range with one PMD and zeros the page. If the allocation fails, it falls back to a 4 KiB page. The application never sees the difference; the only observable effects are fewer faults and, sometimes, higher RSS because the kernel rounded up to 2 MiB.

The second path is asynchronous. The khugepaged kernel thread periodically scans VMAs looking for regions of 512 contiguous base pages that are sufficiently populated and referenced. When it finds a candidate it allocates a huge page, copies the base pages into it, updates the page tables under the mmap lock, and frees the originals. Collapse can succeed even when the original fault path could not obtain a contiguous range, because compaction and reclaim have had time to run in the background. Recent kernels also support multi-size THP (mTHP): smaller power-of-two sizes between 4 KiB and 2 MiB that are still PTE-mapped but reduce fault frequency and, on some architectures, improve TLB density.

When the kernel must split

Not every kernel path understands huge PMDs. The design principle is graceful fallback: any code that cannot handle a huge page calls split_huge_pmd() to turn the PMD into a normal page-table page full of PTEs, or split_huge_page() to break the compound page itself into base pages. Common triggers include:

  • A partial munmap or madvise(MADV_DONTNEED) that does not cover the whole 2 MiB.
  • Copy-on-write on a shared huge page (the fork path must either copy the whole compound page or split it first).
  • Swap-out (older kernels split before swapping; newer ones can swap the whole THP).
  • Migration for NUMA balancing or memory compaction.
  • Any page-table walker that has not been made THP-aware.

Splitting is not free. It requires locks, TLB shootdowns, and, for the compound page itself, reference-count and mapcount updates. Under memory pressure the kernel may also split underused huge pages (those with many zero-filled subpages) so that the unused portions can be reclaimed.

Configuration and policy

System-wide policy lives under /sys/kernel/mm/transparent_hugepage/. The enabled knob is always, madvise, or never. always tries THP for every eligible anonymous mapping. madvise (the common default) restricts it to regions that have called madvise(MADV_HUGEPAGE). never disables the automatic paths, though madvise(MADV_COLLAPSE) can still force a collapse. Per-size controls exist for mTHP. The defrag knob controls whether the fault path is allowed to stall and compact:

  • always — synchronous reclaim and compaction on every THP fault (can produce latency spikes).
  • defer — wake background reclaim/compaction; let khugepaged install later.
  • defer+madvise or madvise — synchronous only for advised regions.
  • never — never compact for THP.

A process can also disable THP for itself with prctl(PR_SET_THP_DISABLE). Embedded or latency-sensitive workloads usually stay in madvise mode and opt in only for large heaps or VM memory.

Interactions that matter

Copy-on-write on a huge page is more expensive than on a base page: the kernel must either copy 2 MiB or split and copy only the faulting page. Fork of a process that has already collapsed large anonymous regions therefore pays either a large copy or a split-plus-later-collapse cost. See the fork article for the base CoW contract.

NUMA balancing may split or migrate THPs. First-touch still decides the node of the original allocation; a later collapse by khugepaged tries to place the huge page on a suitable node. Under memory pressure the reclaim path can split THPs so that individual base pages can be reclaimed or swapped. Compaction, which the OOM and high-order allocation paths rely on, is both a prerequisite for THP success and a source of the latency that makes many operators set defrag=defer.

For KVM guests the benefit is especially large because nested page tables multiply the cost of a TLB miss. That was one of the original motivations for THP.

Misconceptions

THP is free performance. It reduces TLB pressure, but allocation, zeroing, and later splits cost cycles and can inflate RSS. Latency-sensitive services often disable it or restrict it to madvise regions.

It is the same as hugetlbfs. hugetlbfs pages come from a reserved pool, cannot be swapped (until recently for THP), and require explicit application support. THP pages are ordinary compound pages from the buddy allocator and can be split or reclaimed.

Once collapsed, a huge page stays huge forever. Any operation that needs base-page granularity can split it. Underused pages can be split by the shrinker.

mTHP is just a smaller version of the same PMD mapping. Current mTHP sizes are PTE-mapped; they still reduce the number of faults and can benefit from TLB coalescing on some CPUs, but they do not skip a page-table level the way a PMD-sized THP does.

Takeaways

Transparent Huge Pages let the kernel hide 2 MiB (and now smaller) pages behind ordinary anonymous mappings. The fault path tries to allocate them immediately; khugepaged collapses later when contiguous memory becomes available. Splitting is the graceful-fallback mechanism that keeps the rest of the VM correct. The feature is powerful for large working sets and virtual machines, but the compaction stalls, memory bloat, and split costs mean the right policy is usually madvise plus explicit advice on the regions that actually benefit.

Previous Post