Linux の Transparent Huge Pages 設定の読み方:enabled、サイズ別設定、madvise、khugepaged

THP の設定は最上位の enabled とサイズ別の hugepages-<size>kB/enabled に分かれています。inherit、MADV_HUGEPAGE と MADV_NOHUGEPAGE、khugepaged の範囲、/proc と sysfs で実際の使用を確認する方法をカーネル文書に沿って説明します。

開発OSLinuxパフォーマンス

Transparent Huge Pages(THP)は、Linux カーネルが仮想メモリを基本ページより大きなページで自動的に裏付ける機能です。現在のカーネル文書には、/sys/kernel/mm/transparent_hugepage/enabled のほかに、サイズごとに決める設定があります。最上位のファイルだけを読んで THP が「有効」「無効」と判断すると、間違えることがあります。

この記事は、2026年10月13日に確認した docs.kernel.org の文書(ページに表示されたビルド表記は 7.3.0-rc7)と、man-pages 6.19 の madvise(2) に基づいています。カーネルが違えばファイルや値が存在しないことがあるので、使っているカーネルのバージョンの文書と合わせて読んでください。以下のコマンドはすべて読み取り専用です。

THP がすること

カーネル文書は、基本ページを 4KB、huge page を 2MB と仮定して説明しています。実際のサイズは CPU アーキテクチャによって異なります。文書が挙げる利点は二つです。

  • プログラムが初めて触れる 2MB の領域ごとにページフォルトが一度で済み、カーネルへの出入りが 512 分の 1 になります。この効果はマッピングに初めてアクセスするときだけで、フォルトのたびにクリアやコピーするページが大きくなる欠点もあります。
  • より重要なのは、その後のすべてのアクセスです。TLB エントリ一つがはるかに広い仮想メモリを覆うので TLB ミスが減り、ミスしても処理が速くなります。

コストもあります。大きな領域を mmap して 1 バイトしか触らなくても、4KB ではなく 2MB が割り当てられることがあります。システム全体ではなく MADV_HUGEPAGE で印を付けた領域だけで THP を使うモードがあるのは、このためです。

カーネル文書の冒頭は、THP は現在、匿名メモリと tmpfs/shmem でのみ動作すると書いています。ただし同じ文書の khugepaged の説明と madvise(2) は、条件を満たした通常のファイルマッピングを PMD サイズにまとめる場合も扱っています。この記事は主に匿名メモリを扱います。

最近のカーネルには multi-size THP(mTHP)もあります。基本ページより大きく、PMD サイズ(上の例では 2MB)より小さい 16K、32K、64K などのサイズで匿名メモリを割り当てます。mTHP は引き続き PTE でマップされますが、ページフォルトは減り、ページ一つが小さいのでフォルト時のレイテンシの跳ね上がりは PMD サイズよりずっと目立たない、と文書は説明しています。

設定は二層になっている

匿名メモリ用の THP 設定は二か所にあります。

ファイル 受け付ける値 既定値(文書による)
/sys/kernel/mm/transparent_hugepage/enabled always、madvise、never この文書には書かれていない
/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/enabled always、madvise、never、inherit PMD サイズは inherit、それ以外のサイズは never

サイズ別のファイルが inherit なら、そのサイズは最上位の enabled の値に従います。inherit でなければ、サイズ別の値がそのまま適用されます。どのサイズのディレクトリがあるかはシステムによって異なります。複数のサイズが有効なら、カーネルが割り当てごとに最も適したサイズを選びます。

各値の意味は次のとおりです。

  • always:システム全体で THP を使います。
  • madvise:madvise(MADV_HUGEPAGE) で印を付けた領域だけで使います。
  • never:この設定では THP を使いません。ただし、後で説明する例外があります。

最上位の既定値はこの文書には出てきません。ブートパラメータ transparent_hugepage=always|madvise|never で起動時の既定値を変えられる、とだけ書かれています。サイズ別には thp_anon= パラメータを使いますが、これを一度でも指定すると、指定しなかったサイズは PMD サイズも含めてすべて never になります。カーネルコマンドラインに thp_anon= があるかどうかも確認が必要なのはこのためです。

現在の状態は次のように読めます。

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 は PMD サイズの THP の大きさをバイト単位で示します。カーネル文書はこれらのファイルに値を書き込む例だけを示していて、読んだときの出力形式は説明していません。hugepages-*kB ディレクトリが一つもなければ、この記事が基にした文書とは違うカーネルである可能性が高いので、そのバージョンの文書を確認してください。

tmpfs と shmem には別の設定があります。shmem_enabled、サイズ別の hugepages-<size>kB/shmem_enabled、tmpfs のマウントオプション huge= です。この記事では匿名メモリ側だけを扱います。

すべて never でも THP ができることがある

カーネル文書には、sysfs の THP 設定をすべて never にしても THP は完全には無効にならない、と書かれています。madvise(..., MADV_COLLAPSE) がこれらの設定を無視して、範囲を PMD サイズの huge page にまとめるからです。madvise(2) のマニュアルも、MADV_COLLAPSE(Linux 6.1 以降)は /sys/kernel/mm/transparent_hugepage 以下のどの設定とも無関係に動くと説明しています。ただし MADV_NOHUGEPAGE が付いた領域は尊重します。

一つのプロセスで THP を完全に無効にする方法として文書が挙げるのは prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) です。グローバルな設定や MADV_COLLAPSE に関係なくそのプロセスの THP を無効にし、この設定は fork(2) と execve(2) をまたいで引き継がれます。prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0) のように、1 の次の第 3 引数に PR_THP_DISABLE_EXCEPT_ADVISED を渡すと、MADV_HUGEPAGE か MADV_COLLAPSE で要求した領域でだけ THP を許可します。

プロセスが完全に無効な状態かどうかは、/proc/<PID>/status の THP_enabled で確認します。PR_SET_THP_DISABLE で THP を部分的にではなく完全に無効にしたときに 0 になります。

grep THP_enabled /proc/<PID>/status

MADV_HUGEPAGE と MADV_NOHUGEPAGE

madvise モードで THP を受けるには、アプリケーションが自分で領域に印を付ける必要があります。どちらのフラグも Linux 2.6.38 からあります。

  • MADV_HUGEPAGE は指定した範囲で THP を有効にします。カーネルは印の付いた領域を定期的にスキャンして huge page に置き換え、領域が huge page のサイズに合わせて整列していれば最初から huge page を割り当てることもあります。
  • MADV_NOHUGEPAGE は指定した範囲が THP で裏付けられないようにします。

マニュアルにある条件は次のとおりです。

  • これらのフラグは、カーネルが CONFIG_TRANSPARENT_HUGEPAGE 付きでビルドされている場合にだけ使えます。ファイルと shmem のメモリには CONFIG_READ_ONLY_THP_FOR_FS も必要です。
  • どの種類のメモリでも、置き換えられるのは huge page の境界に合った部分だけです。tmpfs を含むファイルマッピングはファイル内でも整列している必要があり、tmpfs 以外のファイルは書き込み用に開かれておらず、マッピングが実行可能でなければなりません。
  • スタックや、VM_NOHUGEPAGE、VM_HUGETLB、VM_IO などのフラグが付いた領域は対象外です。

どの領域にフラグが付いているかは、/proc/<PID>/smaps の VmFlags 行で確認できます。hg は huge page advise フラグ、nh は no huge page advise フラグです。

マニュアルは、MADV_HUGEPAGE はメモリを簡単に無駄にしうると警告しています。アクセスパターンを前もって把握し、メモリ使用量が増えないと分かっている領域にだけ使うように、ということです。また「一般的なカーネル構成は既定で MADV_HUGEPAGE 相当の動作を提供する」とも書いていますが、実際の既定値はディストリビューションとカーネルのビルド設定によるので、自分で確認してください。このマニュアルはサイズ別の mTHP 設定には触れていません。

defrag:割り当てのためにどこまで待つか

THP を使うことになっても、大きな連続した空きメモリがすぐには無いことがあります。そのときの動作は /sys/kernel/mm/transparent_hugepage/defrag が決めます。

値 動作
always 割り当てに失敗したら、その場で直接回収とコンパクションを行って待ちます
defer kswapd と kcompactd を起こしてバックグラウンドで準備し、後で khugepaged が THP を入れます
defer+madvise MADV_HUGEPAGE の領域だけ always のように、それ以外は defer のように動きます
madvise MADV_HUGEPAGE の領域だけ直接回収を行います。文書上の既定値です
never 整理を試みず、すぐに小さなページに戻ります

never にしても、MADV_COLLAPSE は THP を作れます。文書は、defrag に CPU を使った分だけ THP で得をするという期待がいつも当たるわけではない、とも書いています。

khugepaged がすること、しないこと

khugepaged は、メモリをスキャンして小さなページを huge page にまとめるデーモンです。どのサイズでも THP が有効になれば(サイズ別設定か最上位の設定が always または madvise)自動で起動し、すべてのサイズが無効になれば(サイズ別設定と最上位の設定がすべて never)停止します。

範囲には制限があります。

  • ファイルと shmem は PMD サイズにだけまとめます。ほかのサイズにまとめるのは匿名メモリだけです。
  • mTHP にまとめるとき、共有されたページやスワップアウトされたページが一つでもあれば失敗します。
  • MADV_COLLAPSE は PMD サイズにだけまとめ、mTHP は試みません。

設定は /sys/kernel/mm/transparent_hugepage/khugepaged/ 以下にあります。pages_to_scan は 1 回にスキャンするページ数、scan_sleep_millisecs はスキャンの間の待ち時間、max_ptes_none はまとめるときに許す空きページの数です。max_ptes_none が大きいとメモリを多く使うことがあり、小さいとまとめられる回数が減ります。mTHP へのまとめでは 0 と HPAGE_PMD_NR - 1 だけがサポートされ、中間の値を入れると警告を出して 0 として動きます。

khugepaged/pages_collapsed は、進み具合をおおまかに示す値にすぎません。「まとめた」が、PTE のマッピングを PMD のマッピングに置き換えたことも、物理ページを一つの huge page に置き換えたことも指しうるからです。正確な集計には /proc/vmstat とサイズ別の stats ディレクトリを見るよう、文書は案内しています。

最上位の enabled、サイズ別の hugepages-<size>kB/enabled、tmpfs のマウントオプションは、変えても影響するのはこれからの動作だけです。すでに動いているアプリケーションと、khugepaged に登録済みの領域に反映するには、THP を使っていた可能性のあるアプリケーションを再起動する必要があります。

実際に使われているか確認する

設定が有効でも、THP が割り当てられたとは限りません。使用量は /proc で別に見る必要があります。

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

ここで注意があります。AnonHugePages は歴史的な理由でこの名前ですが、数えるのは PMD サイズでマップされた匿名 THP だけです。文書は、本来は AnonHugePmdMapped と呼ぶべきだったと書いています。smaps には PMD レベルでマップされていない THP 専用の項目がないので、64K などの mTHP はこの値に現れません。

項目 場所 意味
AnonHugePages meminfo、smaps PMD レベルでマップされた匿名 THP
ShmemHugePages meminfo huge page で割り当てられた shmem と tmpfs のメモリ
ShmemPmdMapped meminfo、smaps huge page でユーザー空間にマップされた shmem
FileHugePages meminfo huge page で割り当てられたファイルデータ(ページキャッシュ)
FilePmdMapped meminfo、smaps huge page でマップされたページキャッシュ(smaps では shmem/tmpfs を除く)

プロセスごとに見るなら smaps_rollup が便利です。すべてのマッピングの値を合計したファイルで、同じ情報を smaps から求めるよりずっと低いコストで読めると文書にあります。smaps を頻繁に読むと負荷になるという警告もあります。どのマッピングに THP があるかを知りたいときだけ smaps を見れば十分です。

smaps の KernelPageSize と MMUPageSize は THP を使っていても変わらないので、判断の根拠にはなりません。THPeligible が 1 なのは、そのマッピングが現在有効なサイズのどれかで THP を割り当てられる資格があるという意味で、実際に割り当てられたという意味ではありません。

mTHP を含めてサイズ別に見るには、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

サイズ別の統計を公開していないカーネルでは、これらのパスやファイルが存在しないことがあります。その場合、上のコマンドは一致するファイルがないというエラーを出すだけで、値は表示しません。

nr_anon は、そのサイズの匿名 THP がシステム全体にいくつあるかを示します。完全にマップされているものもあれば、一部だけマップされていたり使われていないサブページを含むものもあります。anon_fault_fallback は、そのサイズの割り当てか課金(charge)に失敗して、より小さなサイズや基本ページに戻った回数です。

/proc/vmstat のカウンタが教えてくれること

grep -E '^(thp_|compact_(stall|success|fail) )' /proc/vmstat
カウンタ 増えるとき
thp_fault_alloc ページフォルトで huge page の割り当てと課金に成功したとき
thp_fault_fallback ページフォルトで huge page の割り当てか課金に失敗し、小さなページに戻ったとき
thp_collapse_alloc khugepaged がまとめる範囲を見つけ、新しい huge page の割り当てに成功したとき
thp_collapse_alloc_failed khugepaged がまとめる範囲を見つけたが、割り当てに失敗したとき
thp_split_page huge page が基本ページに分割されたとき。古い huge page の回収がよくある理由です
thp_file_alloc shmem の huge page の割り当てに成功したとき。名前に反して shmem だけを数えます
compact_stall プロセスが huge page を得るためにコンパクションで止まったとき

これらのカウンタは、出来事が起きるたびに増える値です。一度読んだ値より、間隔を置いて二度読み、その差を見るほうが意味があります。thp_fault_fallback が増えていれば huge page を求めたのに得られなかったフォルトがあったということで、compact_stall が増えていれば割り当てのために止まったことがあったということです。止まった時間はこのカウンタでは分かりません。文書は function tracer と mm_page_alloc トレースポイントで測るよう案内しています。

割り当てがうまくいっていることは、アプリケーションが速くなった証拠にはなりません。性能の差はワークロードで別に測る必要があり、これらのカウンタは原因を絞り込むときの材料です。

確認の順序

  1. enabled と hugepages-*kB/enabled を一緒に読み、inherit のサイズは最上位の値に従うことを踏まえて、サイズごとの実際のポリシーを整理します。
  2. /proc/cmdline で transparent_hugepage= と thp_anon= を確認します。
  3. 対象プロセスの THP_enabled と、VmFlags の hg、nh を見ます。
  4. meminfo と smaps_rollup で PMD サイズの使用量を、サイズ別の stats で mTHP を確認します。
  5. /proc/vmstat の fallback とコンパクションのカウンタを、時間を置いて比べます。

カーネル文書には echo で設定を変える例が多くありますが、最上位とサイズ別の enabled は変えてもこれからの動作にしか適用されず、アプリケーションの再起動が必要です。本番のシステムで変える前に、上の順序で現在の状態をまず記録しておいてください。

/proc/meminfo のほかの項目の読み方は、Linux の free コマンドで free が少ないときにまとめています。

ブログへ