Reading Linux Transparent Huge Pages settings: enabled, per-size controls, madvise and khugepaged

THP has a top-level enabled file and per-size hugepages-<size>kB/enabled files. It covers inherit, MADV_HUGEPAGE/MADV_NOHUGEPAGE and khugepaged, and how to check real use in /proc and sysfs.

DevelopmentOperating SystemsLinuxPerformance

Transparent Huge Pages (THP) is the Linux kernel feature that backs virtual memory with pages larger than the base page size, automatically. Current kernel documentation has per-size settings in addition to /sys/kernel/mm/transparent_hugepage/enabled. If you read only the top-level file and conclude that THP is “on” or “off”, you can be wrong.

This post follows the docs.kernel.org documentation as checked on 13 October 2026 (the pages show the build label 7.3.0-rc7) and madvise(2) from man-pages 6.19. On a different kernel some files or values may not exist, so read this alongside the documentation for your kernel version. Every command below is read-only.

What THP does

The kernel documentation assumes a 4 KB base page and a 2 MB huge page in its examples. The real sizes depend on the CPU architecture. It names two benefits.

  • Only one page fault happens for each 2 MB region a program touches, so the program enters and leaves the kernel 512 times less often. This only helps on first access to a mapping, and each fault has a larger page to clear or copy.
  • The bigger effect is on every access after that. One TLB entry covers far more virtual memory, so there are fewer TLB misses, and a miss is handled faster.

There is a cost. If a program mmaps a large region and touches a single byte, it can get a 2 MB page instead of a 4 KB one. That is why a mode exists that uses THP only in regions marked with MADV_HUGEPAGE instead of system-wide.

The opening of the kernel document says THP currently works only for anonymous memory and tmpfs/shmem. The same document’s khugepaged section and madvise(2), however, also cover collapsing regular file mappings to PMD size under certain conditions. This post deals mainly with anonymous memory.

Recent kernels also have multi-size THP (mTHP). It allocates anonymous memory in sizes larger than a base page but smaller than PMD size (2 MB in the example above), such as 16K, 32K and 64K. mTHP pages are still mapped with PTEs, but the documentation says page faults drop, and because each page is smaller, latency spikes at fault time are much less prominent than with PMD-sized pages.

Settings come in two layers

THP settings for anonymous memory live in two places.

File Accepted values Default (per the docs)
/sys/kernel/mm/transparent_hugepage/enabled always, madvise, never Not stated in this document
/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/enabled always, madvise, never, inherit inherit for PMD size, never for every other size

If a per-size file says inherit, that size follows the top-level enabled value. Otherwise the per-size value applies as is. Which size directories exist varies by system. If several sizes are enabled, the kernel picks the most appropriate enabled size for each allocation.

The values mean:

  • always: use THP system-wide.
  • madvise: use THP only in regions marked with madvise(MADV_HUGEPAGE).
  • never: this setting does not use THP. There is an exception, covered below.

The document does not give the top-level default. It only says the boot-time default can be changed with the boot parameter transparent_hugepage=always|madvise|never. Per-size control uses the thp_anon= parameter, and once it appears even once, every size not listed, PMD size included, becomes never. So check the kernel command line for thp_anon= as well.

You can read the current state like this:

cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/enabled
cat /proc/cmdline

hpage_pmd_size gives the size of a PMD-sized THP in bytes. The kernel documentation only shows examples of writing to these files and does not describe what reading them prints. If there are no hugepages-*kB directories at all, your kernel probably differs from the documentation used here, so check the docs for that version.

tmpfs and shmem have their own settings: shmem_enabled, the per-size hugepages-<size>kB/shmem_enabled, and the tmpfs mount option huge=. This post covers only the anonymous memory side.

THP can appear even when everything is never

The kernel documentation states that setting every sysfs THP control to never does not disable THP completely. madvise(..., MADV_COLLAPSE) ignores those settings and collapses the range into PMD-sized huge pages. The madvise(2) manual also says MADV_COLLAPSE (since Linux 6.1) is independent of every setting under /sys/kernel/mm/transparent_hugepage. It does respect regions marked MADV_NOHUGEPAGE.

The way the documentation gives to turn THP off completely for one process is prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0). It disables THP for that process regardless of global settings or MADV_COLLAPSE, and the setting is inherited across fork(2) and execve(2). Calling prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0) instead, with the constant as the third argument after 1, allows THP only in regions requested with MADV_HUGEPAGE or MADV_COLLAPSE.

To see whether a process has THP fully disabled, look at THP_enabled in /proc/<PID>/status. It is 0 when PR_SET_THP_DISABLE has disabled THP completely, not just partially.

grep THP_enabled /proc/<PID>/status

MADV_HUGEPAGE and MADV_NOHUGEPAGE

In madvise mode, an application has to mark regions itself to get THP. Both flags exist since Linux 2.6.38.

  • MADV_HUGEPAGE enables THP for the given range. The kernel regularly scans regions marked this way and replaces them with huge pages, and it can allocate huge pages directly when the region is naturally aligned to the huge page size.
  • MADV_NOHUGEPAGE ensures the given range is not backed by THP.

The manual lists these conditions:

  • The flags are available only if the kernel was built with CONFIG_TRANSPARENT_HUGEPAGE. File and shmem memory also need CONFIG_READ_ONLY_THP_FOR_FS.
  • For every memory type, only parts on huge page boundaries are replaced. File mappings, tmpfs included, must also be aligned within the file, and for non-tmpfs files the file must not be open for write and the mapping must be executable.
  • Stack memory and regions with flags such as VM_NOHUGEPAGE, VM_HUGETLB or VM_IO are not eligible.

The VmFlags line in /proc/<PID>/smaps shows which regions carry a flag: hg is the huge page advise flag and nh the no huge page advise flag.

The manual warns that MADV_HUGEPAGE can very easily waste memory, and says to use it only in regions whose access pattern you know in advance won’t grow the memory footprint. It also says “most common kernel configurations provide MADV_HUGEPAGE-style behavior by default”. The actual default depends on your distribution and kernel build configuration, so check it yourself. This manual does not mention the per-size mTHP controls.

defrag: how hard to try for an allocation

Even when THP is wanted, a large contiguous free block may not be available right away. /sys/kernel/mm/transparent_hugepage/defrag decides what happens then.

Value Behavior
always On allocation failure, stall and do direct reclaim and compaction on the spot
defer Wake kswapd and kcompactd to prepare in the background; khugepaged installs the THP later
defer+madvise Behave like always for MADV_HUGEPAGE regions and like defer for the rest
madvise Do direct reclaim only for MADV_HUGEPAGE regions. The documented default
never Don’t try to defragment; fall back to small pages right away

Even with never, MADV_COLLAPSE can still produce THPs. The documentation also notes that the expectation of gaining more from THP than the CPU time spent on defrag isn’t always met.

What khugepaged does and doesn’t do

khugepaged is a daemon that scans memory and collapses small pages into huge pages. It starts automatically when any THP size is enabled (a per-size or the top-level setting is always or madvise) and stops when all sizes are disabled (per-size and top-level settings are all never).

Its scope is limited.

  • File and shmem memory are collapsed only to PMD size. Only anonymous memory is collapsed to other sizes.
  • An mTHP collapse fails if the range contains any shared or swapped-out page.
  • MADV_COLLAPSE collapses only to PMD size and does not attempt mTHP.

Its settings are under /sys/kernel/mm/transparent_hugepage/khugepaged/. pages_to_scan is the number of pages scanned per pass, scan_sleep_millisecs the wait between passes, and max_ptes_none the number of empty pages allowed when collapsing. A higher max_ptes_none can use more memory; a lower one means fewer collapses. For mTHP collapse only 0 and HPAGE_PMD_NR - 1 are supported; any value in between emits a warning and behaves as 0.

khugepaged/pages_collapsed is only a rough sign of progress, because “collapsed” can mean replacing a PTE mapping with a PMD mapping or replacing physical pages with one huge page. For accurate accounting the documentation points to /proc/vmstat and the per-size stats directories.

Changes to the top-level enabled, the per-size hugepages-<size>kB/enabled and the tmpfs mount option only affect future behavior. To apply them to applications already running, and to regions already registered with khugepaged, you have to restart any application that could have been using huge pages.

Checking whether THP is actually used

A setting being on does not mean THPs were allocated. Usage has to be read separately from /proc.

grep -E '^(AnonHugePages|ShmemHugePages|ShmemPmdMapped|FileHugePages|FilePmdMapped):' /proc/meminfo
grep -E '^(AnonHugePages|ShmemPmdMapped|FilePmdMapped):' /proc/<PID>/smaps_rollup

One catch: AnonHugePages has that name for historical reasons but counts only anonymous THP mapped at PMD size. The documentation says it should have been called AnonHugePmdMapped. smaps has no dedicated entries for THPs that are not mapped at the PMD level, so mTHP such as 64K does not show up here.

Field Where Meaning
AnonHugePages meminfo, smaps Anonymous THP mapped at the PMD level
ShmemHugePages meminfo shmem and tmpfs memory allocated with huge pages
ShmemPmdMapped meminfo, smaps shmem mapped into user space with huge pages
FileHugePages meminfo File data (page cache) allocated with huge pages
FilePmdMapped meminfo, smaps Page cache mapped with huge pages (excluding shmem/tmpfs in smaps)

For a single process, smaps_rollup is convenient. It holds the sums over all mappings, and the documentation says deriving the same numbers from smaps costs significantly more. It also warns that reading smaps frequently adds overhead. Look at smaps only when you need to know which mapping holds THPs.

KernelPageSize and MMUPageSize in smaps don’t change when THP is in use, so don’t rely on them. THPeligible being 1 means the mapping is eligible for THP of some currently enabled size. It does not mean THP was actually allocated.

To see each size, mTHP included, read the files under hugepages-<size>kB/stats/.

grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/nr_anon
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/anon_fault_alloc
grep -H . /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/anon_fault_fallback

On kernels that don’t expose per-size statistics, these paths or files may be missing. The commands then only report that no matching file exists and print no values.

nr_anon is how many anonymous THPs of that size exist in the whole system. Some may be fully mapped, others partially mapped or have unused subpages. anon_fault_fallback counts faults where allocating or charging a page of that size failed and the kernel fell back to a smaller size or base pages.

What the /proc/vmstat counters tell you

grep -E '^(thp_|compact_(stall|success|fail) )' /proc/vmstat
Counter Incremented when
thp_fault_alloc A page fault successfully allocates and charges a huge page
thp_fault_fallback A page fault fails to allocate or charge a huge page and falls back to small pages
thp_collapse_alloc khugepaged finds a range to collapse and allocates a new huge page
thp_collapse_alloc_failed khugepaged finds a range to collapse but the allocation fails
thp_split_page A huge page is split into base pages. A common reason is reclaiming an old huge page
thp_file_alloc A shmem huge page is allocated. Despite the name, it counts only shmem
compact_stall A process stalls to run compaction so a huge page is free

These counters go up each time an event happens. A single reading says little; read twice at an interval and look at the difference. A rising thp_fault_fallback means some faults wanted a huge page and didn’t get one, and a rising compact_stall means something stalled for an allocation. These counters don’t tell you how long the stalls were. The documentation suggests measuring that with the function tracer and the mm_page_alloc tracepoint.

Successful allocation is not evidence that an application got faster. Measure performance on the workload itself; these counters help narrow down the cause.

Order of checks

  1. Read enabled and hugepages-*kB/enabled together and work out each size’s effective policy, remembering that inherit sizes follow the top-level value.
  2. Check /proc/cmdline for transparent_hugepage= and thp_anon=.
  3. Look at the target process’s THP_enabled and the hg and nh entries in VmFlags.
  4. Check PMD-sized usage in meminfo and smaps_rollup, and mTHP in the per-size stats.
  5. Compare the fallback and compaction counters in /proc/vmstat over an interval.

The kernel documentation has plenty of echo examples for changing settings, but a change to the top-level or per-size enabled only affects future behavior and needs application restarts. Before changing anything on a production system, record the current state using the steps above.

For how to read the other fields of /proc/meminfo, see Linux free shows almost no free memory? Read available first.

Back to the blog