メモリコンパクションと断片化対策
空きメモリは十分あるのに巨大ページ確保が失敗する謎が解けます。外部断片化の発生機構、移動可能ページの分離配置、コンパクションによる連続領域づくりまでを内部動作で押さえられます。
- 1.外部断片化は、空きページ総量は足りていても連続した物理領域が取れない状態。buddy systemは隣接ブロックの統合に依存するため、長寿命の非移動可能ページが穴を空けると高次数の確保が失敗する。
- 2.Linuxは物理ページをmigratetype(MOVABLE/UNMOVABLE/RECLAIMABLE)でグルーピングして配置し、移動可能ページと移動不能ページを混ぜないことで連続領域を回収しやすくする。
- 3.メモリコンパクションは移動可能ページを領域の片側へ寄せて連続した空きを作り出す。透過的巨大ページ(THP)やhugetlbの高次数割当を成立させる土台になる。
なぜ空きはあるのに確保が失敗するのか
物理メモリの空き総量が数 GB あるのに、2MB の連続領域(巨大ページ1枚)の割当が失敗する——これが外部断片化(external fragmentation)の典型症状です。原因は、空きページが物理的に飛び飛びに散らばっていて、要求された大きさの連続したフレームとしてまとまらない点にあります。
カーネルの物理ページ割当を担うbuddy systemは、空きメモリを「2のべき乗ページ数のブロック」として管理します。次数(order)N のブロックは 2^N ページ分の物理的に連続した領域です。order 0 が1ページ(典型的に 4KB)、order 9 が512ページ=2MB に相当します。割当はフリーリストから該当次数のブロックを取り、無ければ上位次数を半分に割って供給します。解放時は、隣接する同サイズの相棒(buddy)が両方空いていれば**統合(coalesce)**して上位次数へ戻します。
ここに弱点があります。高次数のブロックを作るには、統合の連鎖が成立する必要があり、それには相棒が空いていることが条件です。連続領域のど真ん中に解放されないページが1枚でも居座ると、その周辺は永遠に統合できず、上位次数のブロックが組み上がりません。
order-0 の空き(.)と使用中(X)が混在する1MB領域:
. . X . . . X . . . . . X . . . ← 空きは多いが…
順に統合を試みても X が相棒側にいると上位次数に上がれない
→ order-9(2MB連続) は1つも作れない
内部断片化は、割り当てた領域の中で使われずに無駄になる端数(4KB ページに 100 バイトだけ載せると残り 3996 バイトが死ぬ等)。外部断片化は、空き同士が連続しないために大きな要求を満たせない状態で、無駄の所在が「ブロック内」か「ブロック間」かで本質的に異なります。コンパクションが対象とするのは後者です。
どのページが穴を空けるのか:移動可能性による分類
外部断片化を悪化させる主犯は、移動も回収もできないページです。なぜなら、空きを連続させたくても、そこに居座るページを動かせなければ穴は埋まらないからです。Linux はこの「動かしやすさ」でページを分類します。これが migratetype(マイグレーションタイプ) です。
| migratetype | 性質 | 代表例 | 断片化への影響 |
|---|---|---|---|
| MOVABLE | 別フレームへコピーして移せる | ユーザー空間の匿名ページ、ページキャッシュ | コンパクションで寄せられる=穴の原因になりにくい |
| RECLAIMABLE | 移せないが回収して空けられる | 一部のスラブ(dentry/inodeキャッシュ等) | shrinkで解放可能、回収すれば連続化に寄与 |
| UNMOVABLE | 移動も回収も不可 | カーネルが直接参照する構造体、DMAバッファ、ページテーブル | 領域に固定され、高次数確保を直接阻害する |
なぜ MOVABLE は動かせて UNMOVABLE は動かせないのか。MOVABLE なページは、その内容を指す参照が仮想アドレス経由のページテーブルエントリ(PTE)だけです。ページを別フレームへコピーし、PTE を新フレームへ張り替えれば、ユーザー空間からは何も変わったように見えません。一方 UNMOVABLE なページは、カーネルが物理アドレスを直に握っている(線形マップ上の固定アドレスを参照している、進行中の DMA の宛先になっている等)ため、物理位置を動かすと参照が壊れます。
領域の大半が MOVABLE でも、UNMOVABLE なページが等間隔に1枚ずつ散らばると、どの 2MB ブロックにも必ず動かせない異物が1個含まれ、巨大ページがまったく作れなくなります。断片化は「空き割合」ではなく「動かせない異物の分布」で決まる、という非直感的な点が急所です。
グルーピングによる予防:混ぜないという設計
コンパクションは事後の治療ですが、その前にコストの低い予防が効きます。Linux のページ割当は、要求された migratetype ごとに**別々の領域(ページブロック単位、典型的に巨大ページと同じ粒度)**から供給しようとします。これが antifragmentation(断片化防止)グルーピングです。
狙いは単純で、移動可能なページと移動不能なページを物理的に混ぜないこと。MOVABLE だけが集まったページブロックは、そこに UNMOVABLE な異物が無いため、後でまるごと寄せて連続空きにできます。逆に最初から両者を混在させると、後から救済できない領域が量産されます。
混在配置(悪い): [M U M M U M U M] [U M M U M M U M] ← どのブロックもUを含む
グルーピング(良い): [M M M M M M M M] [U U M M M M M M] ← 左ブロックは丸ごと寄せられる
M = MOVABLE, U = UNMOVABLE
ただし予防は万能ではありません。メモリ逼迫時に希望の migratetype の空きが尽きると、カーネルは別タイプのページブロックから**借りる(fallback)**しかなく、ここで混在が生まれます。とりわけ UNMOVABLE が MOVABLE 領域へ染み出すと、その領域はもう丸ごとは寄せられません。予防が破れた後の最後の砦がコンパクションです。
メモリコンパクション:動かして連続させる
メモリコンパクション(compaction)は、移動可能なページを領域の一方へ寄せ集め、もう一方に連続した空きを作り出す処理です。ディスクのデフラグの物理メモリ版にあたります。アルゴリズムは、ゾーン内を両端から走査する2つのスキャナで構成されます。
コンパクションの2スキャナ方式:
migrate scanner → (下位アドレスから上へ:移動可能ページを探す)
↓ 見つけたページを空きへコピーしPTEを張り替え
← free scanner(上位アドレスから下へ:空きフレームを探す)
両スキャナが中央で出会ったら1パス終了
- migrate scanner はゾーンの下端から上へ進み、移動可能(MOVABLE/一部 RECLAIMABLE)なページを収集します。
- free scanner はゾーンの上端から下へ進み、空きフレームを収集します。
- migrate 側で見つけたページを free 側のフレームへコピーし、PTE を張り替えて移動を完了します。
これを繰り返すと、ページは下端側に密に詰まり、上端側に連続した空きが生まれます。移動可能ページにしか手を出さないため、ユーザー空間からは PTE が透過的に付け替わるだけでデータの一貫性は保たれます(背景はデマンドページングとページフォルト処理で扱う PTE 操作と同じ機構です)。
移動はタダではありません。ページコピーに加え、移動した各ページについて全 CPU のTLB を無効化(シュートダウン)する必要があり、これは IPI を伴う重い処理です。また、進行中の I/O でロックされたページや、参照カウントが想定外のページは移動できずスキップされ、そうした移動不能ページが多いとコンパクションは目標次数に届かず失敗します。むやみな多用は性能を損ないます。
コンパクションには契機の異なる2系統があります。割当が高次数で失敗したときにその場で同期的に走る direct compaction と、kcompactd カーネルスレッドが事前に非同期で連続領域を仕込んでおく proactive/background compaction です。前者はレイテンシを割当パスに直接乗せ、後者はそれを背景へ逃がします。
巨大ページ割当との関係
コンパクションが最も効くのは**巨大ページ(huge page)**の確保です。x86-64 の 2MB 巨大ページは order 9(連続512ページ)を要求するため、断片化したメモリでは成立しません。巨大ページの利点は、1エントリで広い範囲を覆うことでページングのテーブルウォーク段数と TLB エントリ消費を減らし、TLB ミス率を下げる点にあります。
| 方式 | 確保のタイミング | 連続領域の作り方 | 失敗時の挙動 |
|---|---|---|---|
| hugetlb(明示) | 起動時/管理者が事前確保 | 予約プールを早期に確保し固定 | プール枯渇なら割当失敗(mmapがENOMEM等) |
| THP(透過的巨大ページ) | フォルト時/khugepagedが昇格 | direct/proactiveコンパクションで都度捻出 | 捻出できなければ4KBページにフォールバック |
hugetlb はシステム起動直後の断片化が少ない時点で連続領域を予約プールとして囲い込みます。早いほど成功しやすいのは、時間が経つほど UNMOVABLE が散らばり連続領域が枯れるためです。**透過的巨大ページ(THP, Transparent Huge Page)は、ユーザーが意識せずとも khugepaged が連続した4KBページ群を巨大ページへ昇格(collapse)**し、その昇格に必要な連続フレームをコンパクションが捻出します。THP が確保できなければ通常ページに静かにフォールバックするため動作は壊れませんが、フォールバックが多発すると TLB 効率の利得を取り逃します。
コンパクションはゾーン(≒NUMAノード内の管理単位)ごとに動きます。巨大ページを特定ノードへ確保したいとき、そのノードが断片化していると遠いノードへフォールバックして局所性を損なうことがあります。配置を厳密に取りたい場合はNUMAとメモリアクセス局所性で扱うポリシーと併せ、巨大ページ予約をノード単位で行う設計が有効です。
(1)外部断片化=空き総量は足りるが連続が取れない、内部断片化=ブロック内の端数、の区別。(2)buddy の統合は相棒が空いていることが条件で、UNMOVABLE な異物1枚が高次数確保を阻む理由。(3)migratetype による分離配置は事前予防、コンパクションは事後治療という役割分担。(4)コンパクションは MOVABLE ページのみ移動可で、コストの本体は TLB シュートダウン。この4点が頻出です。
まとめ
- 外部断片化は空きページ総量が足りても連続フレームが取れない状態。buddy system の統合は隣接相棒の空きに依存するため、移動・回収できないページ1枚が高次数割当を阻む。
- Linux はページを migratetype(MOVABLE / RECLAIMABLE / UNMOVABLE)で分類し、移動可能と移動不能を混ぜずにグルーピングして配置することで、事前に連続領域を保ちやすくする。
- メモリコンパクションは migrate/free の2スキャナで移動可能ページを片側へ寄せ、連続した空きを捻出する。コストの本体はページコピーとTLB シュートダウンで、移動不能ページが多いと失敗する。
- これらは **巨大ページ(hugetlb / THP)**の高次数割当を成立させる土台。hugetlb は早期予約、THP は
khugepagedによる昇格+コンパクションで都度捻出し、失敗時は通常ページへフォールバックする。
OS Article
メモリコンパクションと断片化対策を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
メモリ断片化
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
Linuxは物理ページをmigratetype(MOVABLE/UNMOVABLE/RECLAIMABLE)でグルーピングして配置し、移動可能ページと移動不能ページを混ぜないことで連続領域を回収しやすくする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「メモリ断片化 / コンパクション」に近いか確認する。
- 強みである「外部断片化は、空きページ総量は足りていても連続した物理領域が取れない状態。buddy systemは隣接ブロックの統合に依存するため、長寿命の非移動可能ページが穴を空けると高次数の確保が失敗する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。