Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Huge Page まとめ

この文書では、Linux カーネルの機能を中心に、 huge page に関連するメモ リ管理の機能についてまとめる。概観とキーワードの把握、およびリファレン スの提供を目的である。特に non-trasparent な huge page と transparent huge page の違いとそれらの境界を把握することをひとつの主眼としている。 そのため、詳細さと網羅性はスコープ外である。

前置き、または免責

  • Intel x86_64 上で Linux を動かす状況を想定している。アーキテクチャ 依存、カーネル依存な事項は、一部触れられている部分もあるが、それ以上 ではない。
  • Linux kernel について、バージョン依存、config 依存な部分はスコープ外 である。動作確認は CentOS 7.2 で行った。

目次

Table of Contents

略語, 特殊な言葉使い

一般的な略語:

  • TLB: translation lookaside buffer
  • THP: transparent huge page

以下は、このドキュメントでつかう特殊であろう言葉使い:

  • ユーザーアプリケーション: ユーザー空間で動作するプログラム。単にアプ リケーションと書くこともある。また動作している状態を指すときには、ユー ザープロセスと書くかもしれない。
  • THP ページ: THP 機能で管理されている huge page ページ。THP の P が page の頭文字なのでページがカブっているが、hugetlb support (=non-transparent huge page) 管理下の huge page と区別するために あえてカブせる。
  • huge page: カタカナで書くと "ヒュージページ" があまりにもっさりするので これは英語にしておく。複数形は気にしない。

メモリ管理におけるページとそのサイズ

x86_64 上の Linux では、通常 4KB 単位にメモリを分割し管理していた。こ の 4KB の管理単位をページと呼ぶ。通常、ページサイズはプロセッサにより 決まっている。その後、メモリ容量の増大に伴い、メモリ管理の負荷軽減(メ モリ消費量、TLB miss の削減)のため、プロセッサはより大きなページサイズ をサポートするようになり、現在では複数のページサイズが利用可能なことが 多い。Linux では 4KB を超えるページを huge page と呼んでいる。

なお、Linux では huge page と呼ばれるが、FreeBSD では superpage, Windows ではlarge page と呼ばれるようである [1] [2]。 Intel における呼 称としては、 [3] に 2MB and/or 1GB のページサイズに対して、large page が登場している。

メモリ管理1: プロセッサ

まずハードウェアの観点から始めよう。プロセッサにより、利用できるメモリ のページサイズが決まり、プロセッサのメモリ管理、TLB キャッシュミスの処 理が関係してくる。

Intel x86_64 は TLB miss の処理をハードウェアで行うため [3] , カーネ ル(ソフトウェア)はページエントリの配置を正しく行うこと [4]、そしてコン テキストスイッチの際などに TLB をクリアすることなどが仕事になる。蛇足 だが、ページフォルト処理のうち、スワップアウトの場合もカーネルの仕事で ある。

現状、Intel x86_64 では、4KB に加えて、2MB と 1GB のページサイズをサ ポートしている。例として [5] から抜粋すると、Intel Skylake microarchitecture における TLB は、キャッシュ階層と類似してLevel 1 が instruction と data に分かれており、Level 2 は統合して(unified)使われ ている。L1 instrunction と data はそれぞれ ITLB と DTLB、また L2 TLBは STLB (second level TLB)と呼ばれることもある [7]。カッコ内は entry 数を 示している。

  • ITLB: 4KB (128), 2MB/4MB (8/thread) の2種類
  • DTLB: 4KB (64), 2MB/4MB (32), 1GB (4) の3種類
  • STLB: 4KB と 2MB/4MB の共有 (1536), 1GB (16) の2種類

簡単な算数をしてみると、STLBの 1536 エントリを 4KB で埋めると 6MB 分の メモリが、2MB で埋めると 3GB 分のメモリが TLB に乗ることになる。

同ドキュメントの Haswell microarchitecture からも同様に抜粋する [6]。

  • ITLB: 4KB (128), 2MB/4MB (8/thread) の2種類
  • DTLB: 4KB (64), 2MB/4MB (32), 1GB (4) の3種類
  • STLB: 4KB と 2MB/4MB の共有 (1024)

Haswell から Skylake へ、以下の変更点がある。

  • ITLB, DTLB は (associativity を除き)同じ構成
  • STLB における "4KB と 2MB/4MB の共有" のエントリ数が 1024 -> 1536 と増加
  • STLB に 1GB ページサイズが新設

ちなみに Itanium 2 (IA-64) では、4K, 8K, 16K, 64K, 256K, 1M, 4M, 16M, 256M バイトのページサイズがサポートされている(らしい) [8]。

メモリ管理2: カーネル

huge page に関する Linux カーネル の機能(インターフェース)は大きくふた つあり、それぞれ "hugetlbpage support" と "Transparet Huge Pages" (THP) と呼ばれる。まず、"hugetlbpage support" は、プロセッサの複数ペー ジサイズサポートを、実直にユーザ空間から利用可能にする機能である [9]。 この機能の範囲内では、huge page の利用には、次の2つの明示的なステップ が必要となる。

  1. /proc/sys/vm/nr_hugepages を通じた事前のアロケーションと
  2. mmap(2) への明示的なフラグ追加、または 特殊なファイルシステム hugetlbfs を通じたメモリ確保

つまり、この hugetlbpage support では huge page に割り当てるメモリ量の 制御がユーザアプリケーション実行前に必要であることと、ユーザアプリケー ション側で huge page を想定したメモリアロケーションが必要となる。例え ば、データベースアプリケーションのように、自身が動作するカーネル管理下 のほぼすべてのリソースを使用し、かつメモリ使用が性能に大きく影響するタ イプのアプリケーションでは huge page を直接利用することが望ましい。一 方で、複数の種類のアプリケーションが共存し、利用可能なリソースが動的に 変わる場合や、アプリケーション自体の対応がなされていない場合には hugetlbpage support からは huge page の恩恵を受けることが出来ない。

そこでもう一つのインターフェースとして Transparent Huge Pages (THP) が 存在する [10]。これは、事前アロケーションが不要であり、またユーザアプ リケーションから huge page 利用の明示がなくとも huge page を仮想メモリ のバックエンドとして割り当てる機能である。そのため、THP の内部では、メ モリ確保要求に応じて huge page 割り当てるだけではなく、huge page 割り 当てが出来ない場合にnormal page 割り当てへのフォールバック、メモリの断 片化の監視、必要に応じたコンパクション(デフラグ)など、複雑な処理が行わ れている。

hugetlbpage support

明示的なインターフェースである hugetlbpage support を見てみる [9]。 "hugetlbpage support" は長ったらしいので簡潔な名前がほしいがいまいち統 一的な明確な呼び名が分からない。シンプルに huge pages と呼びたいが、後 述の THP でも "huge page" という使い方をしている。区別したい場合は non-transparent huge page と呼ばれることもある。

サンプルコード

ユーザアプリケーション側で明示的に huge page を指示するので、コードサ ンプルを見るのがわかりやすい。Linux カーネル内のサンプル ([9] の Examples より抜粋) として、次のものがある。

mmap の例から抜粋すると、骨は次のようになる。

#define MAP_HUGETLB 0x40000 /* arch specific */
#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)

int main(void) {

    void *addr;
    addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, 0, 0);

    unsigned long i;
    for (i = 0; i < LENGTH; i++)
        *(addr + i) = (char)i;

一方、shared memory (shm) の例は次のとおりである。

#define ADDR (void *)(0x0UL)
#define SHMAT_FLAGS (0)
...
int main(void) {
    ...
    shmid = shmget(2, LENGTH, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
    ...
    shmaddr = shmat(shmid, ADDR, SHMAT_FLAGS);
    ...

    for (i = 0; i < LENGTH; i++) {
        shmaddr[i] = (char)(i);
    }

使用状況 /proc/meminfo

まず、もっともざっくりした huge page の使用状況は procfs /proc/meminfo から取得できる。

$ cat /proc/meminfo
[snip]
HugePages_Total:    1024
HugePages_Free:     1024
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
[snip]

各項目の意味は次の通りである。

  • HugePages_Total: huge page プールのサイズ。
  • HugePages_Free: プール内の allocate されていないページ数。
  • HugePages_Rsvd: Rsvd は reserved の略。プール内で、allocate され 得ることが決まっているが、まだ allocate されていない page 数。 reserved なページは、ページフォルト時にアロケートできることを保証する。
  • HugePages_Surp: プール内で /proc/sys/vm/nr_hugepages の数を超えているページ数。 /proc/sys/vm/nt_overcommit_hugepages でコントロールされる。 Surp は surplus (余剰の)の略。
  • Hugepagesize: デフォルトの huge page のページサイズ

例として、mmap(2) から huge page をマップした場合を考える。この場合、 CoW (copy-on-write) として動作するため[11]、 mmap 呼び出し時には実メ モリは確保されず、HugePages_Rsvd がカウントアップされるが、 HugePages_Freeは変更されない。その後、メモリ領域への書き込みアクセス が発生すると、page fault 処理の中で実メモリのアロケートが行われ、 HugePages_Rsvd は減算され、またHugePages_Free も減算される。

ページ数上限設定, NUMA 環境

利用する huge page 数の上限は /proc/sys/vm/nr_hugepages で設定する。例:

$ echo 1024 > /proc/sys/vm/nr_hugepages

NUMA 環境において、huge page 確保のメモリポリシーは、 nr_hugepages_mempolicy を変更するタスクの NUMA メモリーポリシーで制 御できる。次に例を示す。

$ numactl --interleave <node-list> echo 20 \
      /proc/sys/vm/nr_hugepages_mempolicy

メモリポリシーモードは任意のもの bind , preferred, local, interleave が使用できる。

注意 huge page はメモリが不足しそうになってもスワップアウトされない。

複数ページサイズ

環境により、2MB と 1GB など、複数のサイズをもつ huge page を同時に利用 することが出来る。

Linux on x86_64 の場合、プロセッサがサポートするページサイズは /proc/cpuinfo で確認できる [2] 。 2MB ページサイズサポートの場合は pse 、1GB ページサイズサポートの場合は pdpe1gb が出力の flags に 含まれる。例を示す。

% grep pse /proc/cpuinfo | uniq
flags           : [...] pse [...]

各ページサイズの huge page の設定、および使用状況確認には、 /sys/kernel/mm/hugepages/hugepages-<size>kB 以下のファイルを読み書きする。

$ ls /sys/kernel/mm/hugepages/hugepages-2048kB/
free_hugepages  nr_hugepages  nr_hugepages_mempolicy
nr_overcommit_hugepages  resv_hugepages  surplus_hugepages

$ ls /sys/kernel/mm/hugepages/hugepages-1048576kB/
free_hugepages  nr_hugepages  nr_hugepages_mempolicy
nr_overcommit_hugepages  resv_hugepages  surplus_hugepages

デフォルトのページサイズは /proc/meminfoHugepagesize に示され る。また上述の /proc/sys/vm/nr_hugepages はデフォルトサイズの huge page のページ数指定である。

NUMA ノードごとの情報は /sys/devices/system/node/node[0-9]*/hugepages/ から 取得できる。

$ ls /sys/devices/system/node/node0/hugepages/hugepages-2048kB/
free_hugepages  nr_hugepages  surplus_hugepages
  • free_hugepagessurplus_hugepages は読み取り専用であり、対応ノー ドの利用可能ページ数と overcommit されたページ数を返す。
  • nr_hugepages は対応するノードの huge page 数を返す。書き込みが発生 した場合、対応するノードの huge page 数がその値に調整される。
  • ここで、 resv_hugepages はグローバルな情報なため、このリストには無 いことを注意しておく。

TODO 原文[9]は "Note that the number of overcommit and reserve pages remain global quantities, ...", とあるが、 直前で surplus_hugepages は ノード単位と言っている。ノード単位のほうが正しそうに思えるが要確認

実ページ確保時のゼロクリアにおけるカーネルのバグ

上述の CoW による実メモリアロケーションの際、カーネルはメモリ領域をゼ ロクリアした後にユーザ空間へ渡す。

この時のスタックはページフォルト処理を含め、次のようになる(環境により異なる)。

`<pagefault 発生>` →  `handle_mm_fault` →
  `do_huge_pmd_anonymous_page` → `clear_page_c_e`

このとき、ページをゼロクリアする際にグローバルな mutex により、実質シ ングルスレッドでの実行になってしまう重大な "バグ" [12][13] がある。こ の状況にあたると、マルチコア環境で System CPU が 1 コアのみ 100% とな り、他のコアが活用されない症状が発生する。このバグは Linux 3.15 で修正 されている。Red Hat Enterprise Linux 7 系列は 3.10.x であるため、この バグの影響を受ける。

2016-07-19 時点でメジャーな他のディストリビューションは、Ubuntu 16.04 (LTS) は kernel 4.4, Debian 8 (jessie) は kernel 3.16, Fedora 24 が kernel 4.5 と、すでに修正済みのバージョンを使用している。

Transparet Huge Pages (THP)

上記の hugetlbpage support では、ユーザアプリケーション自体の対応が必 要になる。その解決策/回避策として Transparent Huge Pages (THP) では、 ユーザアプリケーションの変更なしに、確保されるメモリのバックエンドを通 常(4KB)ページと huge page を自動で切り替えることを主眼としている。

malloc でメモリを確保しただけで、カーネルが状況に応じて huge page の 実メモリを確保してユーザに見せるマッピングを作ってくれる。ユーザアプリ ケーション側ではpage size へのアラインメントやその整数倍のメモリ領域を 要求するといったロジックは不要である。その反面、メモリのコンパクション、 マイグレーションなどの処理を負うことになる。

以下、[10] にある設定項目、監視項目を見ていく。

有効、無効設定

まず THP 全体に対する有効、無効の設定がある。 /sys/kernel/mm/transparent_hugepage/enabled に以下の値を書き込むこと で切り替えできる。

$ echo always > /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled

同じ項目に対して、もうひとつの可能な値は madvise である。

$ echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

この場合、THP は madvise(2) から MADV_HUGEPAGE が付与された領域の みで有効になる[14]。特に、huge page の対応をしていないアプリケーション が確保したメモリ領域に関しては THP は無効になる。

次にページアロケート要求時のデフラグに関する設定を見てみる。THP では、 ページアロケート要求のタイミングで THP が即座にアロケートできない場合 にいくつか選択肢があり、次の値で切り替える。

echo always >/sys/kernel/mm/transparent_hugepage/defrag
echo never >/sys/kernel/mm/transparent_hugepage/defrag
echo madvise >/sys/kernel/mm/transparent_hugepage/defrag
echo defer >/sys/kernel/mm/transparent_hugepage/defrag
  • always: THP が有効。この設定では、アプリケーションがメモリを要求し たが THP が即座にアロケートできない時に、アプリケーションを待たせた 状態で reclain/compaction を行い THP をアロケートしようとする。
  • never: デフラグが無効。
  • madvise: MADV_HUGEPAGE の領域のみデフラグを行う。その領域では always と同様。
  • defer: THP が有効。alyways と異なり、THP が即座にアロケートでき ない場合はnormal page にフォールバックし、 relcaim/compaction は kcompact に任せる。

NOTE defer は パッチ [15] (メールの日付は Fri, 26 Feb 2016) で追加 され、 4.6 で入ったもよう[未確認]。CentOS 7.2 (kernel 3.10.0) では設定 できない。

上記のアロケート要求時に加え、バックグラウンドで khugepaged でもデフ ラグ処理が行われる。これは次のように有効、無効を設定できる。

echo 0 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag
echo 1 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag

ページアロケートに際して、デフォルト設定では、THP はまず huge zero page (read only のゼロ埋めされたページ)を使い、CoW で実メモリをアロケー トしようとする。このように zero page を使うか否かは次のように設定でき る。

echo 0 >/sys/kernel/mm/transparent_hugepage/use_zero_page
echo 1 >/sys/kernel/mm/transparent_hugepage/use_zero_page

NOTE なお、huge page の説明中で言及したページゼロクリア時のカーネ ルバグ (< 3.15) はTHP 利用時にも該当する。

監視1: THP ページの使用量

システム全体で使われている THP のサイズは /proc/meminfoAnonHugePages から取得できる。

$ cat /proc/meminfo | grep AnonHugePages
AnonHugePages:   1533952 kB

プロセスごとの THP サイズは /proc/<PID>/smaps から AnonHugePages を合計することで求めることが可能である。しかし smaps の読み込は高価 なため頻繁なアクセスは負荷をかけることになる。

$ sudo cat /proc/15511/smaps | grep AnonHugePages | \
     awk '{sum+=$2; unit=$3;}END{print sum, unit}'
180224 kB

TODO /proc/vmstatnr_anon_transparent_hugepages に huge page size を掛けると AnonHugePages になるようだが、前者のドキュメントは検 索した限り存在しないようだ。ソース[16]を読み解けば分かるはずではあるが。

監視2: /proc/vmstat

/proc/vmstat から幾つかピックアップして監視すると良い(かもしれない) 項目を記す。[10] は(完全ではないが)より網羅的なリストを含んでいるので ここにない項目はまずそちらを参照するのが良い。

  • thp_fault_alloc: THP ページがアロケートされたときにインクレメント される。ゼロページ使用時(use_zero_page=1, CentOS 7.2 ではデフォル ト)には、CoW として、最初の書き込みアクセス時にページフォルト処理の 中でアロケートされ、 この数値がカウントアップされる。
  • thp_collapse_alloc: khugepaged が一つの THP ページにまとめられる normal pages の範囲を見つけ、実際に THP ページをアロケートできた時に インクレメントされる。
  • thp_fault_fallback: ページフォルト時に THP ページアロケートに失敗 してsmall page (=nomal page) にフォールバックした時にインクレメント される。
  • thp_split: huge page が normal page に分割されるときにインクレメン トされる。
  • compact_stall: ユーザプロセスがメモリコンパクションでストールする ときにインクレメントされる。
  • compact_pages_moved: ページが移動された時にインクレメントされる。 この値が急速に上昇している場合は、データコピーが頻発しているため、 そのコストが TLB miss のコストを上回っている可能性がある。

NOTE thp_split は kernel 4.5 で thp_split_page, thp_split_fail, thp_split_pmd の3つに分割された。RHEL 7 系列の場合 は kernel 3.10 の Document [19] を参照すると良い。

落ち穂拾い

関連するかもしれないユーティリティ

cpuid コマンド

CPUID 情報(CPUID(4), /dev/cpu/CPUNUM/cpuid)をテキスト形式変換して 表示してくれるコマンドである。CentOS 7.2 では cpuid パッケージに含ま れる。この中には Level ごとの TLB の情報も含まれる。

lstopo コマンド

Portable Hardware Locality (hwloc) に含まれるユーティリティーである。 CentOS 7.2 では hwloc パッケージには text 版の lstopo-no-graphics が、hwloc-guilstopo が含まれている。NUMA ノードやコア、キャッ シュの改装をグラフィカルに表示してくれる。しかし TLBの情報は載らないよ うだ。公式サイトにいくつかのサンプルが置いてある [18]。

Tips: ワンライナー

/proc/meminfo から hugetlbpage に関係する行を拾う

$ cat /proc/meminfo | grep Huge
AnonHugePages:   1540096 kB
HugePages_Total:    1024
HugePages_Free:     1024
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

/proc/vmstat から THP に関係する行を拾う。

$ cat /proc/vmstat | egrep '(hugepage|thp|compact)'
nr_anon_transparent_hugepages 752
compact_migrate_scanned 183738178
compact_free_scanned 18461965374
compact_isolated 28161384
compact_stall 49123
compact_fail 34706
compact_success 14417
thp_fault_alloc 4350509
thp_fault_fallback 404443
thp_collapse_alloc 125552
thp_collapse_alloc_failed 17
thp_split 131276
thp_zero_page_alloc 37
thp_zero_page_alloc_failed 84

References

以下、本文から参照していないが、huge page まわりで有用な文献をリストする。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.