ページ置換アルゴリズム

ページ置換アルゴリズム（ページちかんアルゴリズム）とは、仮想記憶管理としてページング方式を使用するコンピュータのオペレーティングシステムにおいて、空き物理ページが少ない状態で新たなページを割り当てなければならないときにどのページを「ページアウト（スワップアウト）」するかを決定する方法を意味する。これはページフォールトが発生したときに使用可能なフリーなページが存在しないときに発生する。厳密には発生条件はシステムの種類や設定によって異なるが、フリーなページが全く無い場合か、あらかじめ設定したしきい値よりもフリーなページ数が少ないときに発生する。
以前にページアウトすべきページとして選択され置換されたページに再度アクセスが発生したら、そのページをページインする必要がある。そして、これにはI/Oの完了を待たなければならない。この、ページインを待つ時間の累計が小さいほどページ置換アルゴリズムが優秀であると言える。ページ置換アルゴリズムはページへのアクセスに関するハードウェアからの限られた情報を見て、アルゴリズム自身にかかる時間とページインにかかる時間のバランスをとりつつ、ページミスのなるべく起きない置換をしなければならない。
ページ置換アルゴリズムはオンラインアルゴリズムの一種である。

ローカルな置換とグローバルな置換

置換アルゴリズムは「ローカル」と「グローバル」に分けられる。あるプロセスがページフォールトを発生したとき、ローカルなページ置換アルゴリズムではそのプロセス（あるいはそのプロセスが属するメモリ・パーティションを共有するプロセスグループ）が現在使用しているページ群からページを選択して置換する。一方、グローバルなページ置換アルゴリズムではメモリ上の任意のページを選択する。
ローカルなページ置換では、ある種のメモリ・パーティションが前提となっており、プロセスあるいはプロセスグループに対して割り当て可能なメモリページ数を決定している。最も一般的なパーティション手法としては「固定パーティション」とワーキングセットモデルに基づいた「バランスセット」アルゴリズムがある。ローカルなページ置換の利点はスケーラビリティが良いことである。各プロセスはページフォールトに独立して対処できるので、グローバルなデータ構造に必要以上に関わる必要がない。

事前クリーニング

最も教科書的な置換アルゴリズムは、結果としてページを選択して返す。このページが変更されている場合（そのページを再利用する前に内容を二次記憶装置に書き戻す必要があることを意味する）、ページ内容をディスクなどに書き出して クリーンなページにしなければならない。初期の仮想記憶では、このクリーニング処理のオーバーヘッドは問題とはならなかった。というのは、当時のシステムでは全二重チャネルで二次記憶装置を接続していたため、ページインを並行して実行できたのである。最近の商用ハードウェアでは、全二重転送はサポートしていないため、クリーニングが問題となるのである。
これに対処するために、様々な事前クリーニングポリシーが実装された。事前クリーニングとは、間もなく再利用される予定の（内容を変更されている）ページについて I/O を開始する機構である。考え方は、次にページアウト対象として選ばれるページを事前にディスクに書き戻しておけば、実際にページアウト対象として選ばれたときにはI/Oをする必要がないということである。事前クリーニングは次に選択されるページが事前にわかることを前提としている。あまり積極的に行うと、選択されたときにはまたクリーニングが必要な状態になってI/Oバンド幅を浪費することになる。結局、スループットを優先するか、ターンアラウンドタイムを優先するかという選択になり、システムの性格によって最適な落とし所は変わってくる。

先行ページング

デマンドページングを採用しているシステムもあり、実際に要求を受けてからページの内容をRAMにロードする。
一方、RAM上にないどのページが間もなく必要とされるかを推論し、そのようなページを要求される前にRAMへロードするシステムもある。これに事前クリーニングを組合わせることが多く、RAM上にあるページのうちすぐには必要とされないページを推測し、それを事前に二次記憶装置に書き戻しておく。
ページフォールトが発生したとき、「先行ページング」システムでは参照されたページを持ってくるだけでなく、参照のあったページに続く数ページも同時にロードしておく。CPUが命令プリフェッチで数個の命令を事前にフェッチしておくのと同様である。
間もなく必要になると思われるページ群を不連続であっても事前にロードする方式を「スワッププリフェッチ」と呼ぶ。

各種アルゴリズム

ページ置換アルゴリズムには様々なものがある。

理論上最適なページ置換アルゴリズム

理論上最適なページ置換アルゴリズム（OPT、千里眼置換アルゴリズム、Béládyの最適ページ置換アルゴリズムとも呼ばれる）とは、以下のようなものである。ページをスワップインする必要が生じたとき、オペレーティングシステムが現在メモリ上にあるページを全て調べて、次にページが使われるまでの時間を測る。そして、OSは最も長い期間使われないページをスワップアウトする。たとえば、今後6秒間使われないページをスワップアウトして、今後0.4秒間使われる予定のページに割り当てる。
しかし、このアルゴリズムを実際に（少なくとも汎用の）オペレーティングシステムに実装することは現実的ではない。ただし、実行されるソフトウェアが全て判っていて、それらのメモリ使用パターンも分析済みであるか、実行時に分析することが可能な場合は不可能ではない。とは言うものの、OPT に近い性能を示すアルゴリズムは存在する。あるソフトウェアを最初に動作させたときに、オペレーティングシステムが使われたページを全て記録する。そしてこのデータを使って二度目以降にそのソフトウェアを動作させるときに、このデータを使ってページ置換を行うのである。この手法は OPT に近い性能を保証するが、それは二度目の動作のときであり、初めて動作させるソフトウェアでは無効である。また、動作するたびにメモリ参照パターンが大きく変化するプログラムでは効果がない。
ページング問題の解析はオンラインアルゴリズムの分野の話題である。ページング問題向けの乱択オンラインアルゴリズムの効率はならし解析を使って測定される。

FIFO (First-In First-Out)

FIFOページ置換アルゴリズムはオーバヘッドの少ないアルゴリズムのひとつで、OS内でちょっとした記録をとる。名前からもわかるとおり、プロセスに割り当てたページを割り当てた順番にキューに載せる。ページ置換が必要になったとき、キューの先頭のページ（最も古いページ）を選択する。FIFO構造はコストがかからないし簡単なのだが、実際のアプリケーションにこのアルゴリズムを適用すると性能は悪くなる。そのため、そのままの形で使われることはない。このアルゴリズムではBéládyの例外と呼ばれる状況が発生しうる。
FIFOページ置換アルゴリズムはVAX/VMSで、若干修正された形で使われている。有効な変換テーブル参照において限定された数のエントリをスキップすることで、後述のセカンドチャンス方式を部分的に取り入れている。そしてさらに、置換されたページをプロセスのワーキングセットからシステム全体のプールに移すが、そういったページが再利用される前に再び当該ページの内容が必要になった場合は、そのまま使える。

クロック

クロック（時計）とは、FIFOをセカンドチャンスよりも効率化した方式で、ページを定期的にリストの後方に押しやる必要がなく、セカンドチャンスと同等の機能を実現するものである。クロックアルゴリズムではページを円環状のリストとして扱い、そのリスト上の最も古いページを指す「針」を使用する。ページフォールトが発生して空きがないとき、針が指している位置から参照ビットのチェックを行う。参照ビットが 0 なら、針の指しているページを再利用し、0 でないなら参照ビットをクリアして針をインクリメントする。そして、置換可能なページが見つかるまでこれを繰り返す。

LRU (Least Recently Used)

LRU（最近使われていない）ページ置換アルゴリズムは NRUと名前は似ているが、LRUでは短期間のページ使用履歴を保持している点が異なる。NRUではクロック割り込み間隔の使用/不使用で判断していた。LRU は、最近よく使われているページはその後の短時間でもよく使われるだろうという判断に基づいている。LRU は理論上は OPT に近い性能を発揮するはずだが、実際に実装するとコストがやや高い。このコストを抑える試みをした実装手法がいくつか存在している。
最もコストの高い手法はメモリ上の全ページを含むリンクリストを使う方法である。このリストの最後尾はLRUページであり、先頭は逆に最近使われているページである。この実装のコストはメモリ参照の度にリスト上のアイテムを移動させなければならない点であり、これには非常に時間がかかる。
別の方法はハードウェアのサポートを必要とする。ハードウェアで 64ビットのカウンタを命令実行毎にカウントアップする。ページにアクセスがあったときは、その時点のカウンタの値をページに与える。ページを置換するとき、オペレーティングシステムは単純に最も小さなカウンタ値を持つページを選択する。ただし、現在のハードウェアにはそのような機構はないので、OSが全ページについてカウンタを調べる必要があり、現実的ではない。
LRU アルゴリズムの重要な利点は、完全な統計分析を修正できる余地がある点である。たとえば、OPT アルゴリズムに比較して N回以上ページフォールトが多くなることはない、ということが証明されている。ここで、N は管理対象のページ数に比例した値である。
実装コストがかかるため、LRUに似ているがより実装コストの低いアルゴリズムを考慮することもある。
一方 LRU の欠点は、一般的な参照パターンで性能を低下させてしまう傾向があることである。たとえば、LRU 対象のページが N 個あったとして、あるアプリケーションが N + 1 ページにまたがる配列にアクセスしながらループしているとき、インデックスの進みと共にページフォールトが発生してしまう（つまり、配列が大きすぎるので必ずページ置換は必要だが、LRUを使うと次にアクセスするページが必ずページアウトされてしまう）。大きなループを使うのは一般的なので、このような場合に対応できるようにLRUを改善する試みは多く行われてきた。最も多い手法はループしてアクセスしているパターンを検出して、適した置換アルゴリズム（たとえば、MRU（Most Recently Used）に切り替えるのである。

ランダム

ランダム置換アルゴリズムはランダムにページを選んで置換する。おかしな話と思われるかもしれないが、このアルゴリズムはある状況では役に立つ。一般に FIFO よりも性能がよく、ループしたメモリ参照でも LRU より性能がよい。OS/390は グローバルなLRU予測をしているが、LRUの性能が悪くなったことを検出するとランダム置換に切り替えるようになっている。Intel i860 プロセッサもランダム置換方式を採用していた。

NFU (Not Frequently Used)

NFU（頻繁には使われていない）ページ置換アルゴリズムも、カウンタを必要とするが、この場合は各ページが初期値ゼロのカウンタを持つ。クロック割り込みの度に前回のクロック割り込み以降参照のあったページ全てのカウンタを 1 だけカウントアップする。結果としてカウンタはそのページがどれだけ頻繁にアクセスされているかを示すことになる。したがって、最もカウンタの値の小さいページを必要に応じてスワップアウトする。
NFU の主な問題点は、使用回数だけで頻度を求めているために、どれだけ本当に頻繁かがわからないことである。したがって、マルチパスコンパイラの動作を考えてみると、最初のパスで頻繁にアクセスしたメモリは次のパスではアクセスされないにも関わらず、全体的に使用回数で見れば最初のパスでアクセスしていたページの方が多いために、二回目のパスで実際に使用しているページがスワップアウトされてしまうことになる。そうなると性能は低下する。うれしいことに、これに似ているがもっとよいアルゴリズムがある（エージング）。
ページテーブルがヌルポインタ値を含むとき、NFUはLRUよりもページフォールトの発生が少ない

エージング

エージング・アルゴリズムは NFU アルゴリズムを改良したもので、ページを使用した時間間隔を考慮するようにしたものである。ページ毎のカウンタを単純にインクリメントする代わりに、時間に応じて参照に重み付けをする。ページの参照カウンタは右シフト（2で割るのと同じ）してからカウントアップされる。たとえば、あるページのクロック割り込み 6回分の参照ビットが、1 → 0 → 0→ 1 → 1 → 0 だった場合、その参照カウンタは、10000000 → 01000000 → 00100000 → 10010000 → 11001000 → 01100100 のように変化する。これを見てもわかる通り、最近の参照に対応した値は大きく、過去に遡るほど値が小さくなる。これにより参照回数がすくなくても最近参照されたページが高い優先順位を持つことになる。したがって、置換が必要なら、このカウンタ値が小さいページを選択する。
LRU と エージングは違うことに注意されたい。エージングは限られたクロック割り込み回数ぶんしか参照履歴を持たない（そのプロセッサの整数のビット幅により、16回だったり 32回だったりする）。カウンタが8ビットの場合、9 tick（クロック割り込み間隔のこと）前に参照があっても 1000 tick 前であっても、カウンタはゼロになってしまう。一般に、最近の 16 tick ぶんの履歴があればスワップアウトすべきページを決定するには十分である。したがって、エージングは低コストで OPT に近い性能を発揮する。

参照ビットを持たないハードウェアでの技法

これまで解説してきた技法の多くは、各ページに対応した参照ビットが存在することを前提としている。中にはそのようなビットを持たないハードウェアもあり、その場合に効率的にページ置換を行うにはテクニックを要する。
よく知られた例として、VMSの動作するVAXがある。その手法は Secondary Page Caching と呼ばれている。ワーキングセットから除かれたページはすぐには削除されず、一時的に2つのリストのいずれかに置かれる。二次記憶装置の対応する内容が有効なページ（書き換えられていないページ）は Free Page List の最後尾に置かれ、書き換えられているページは Modified Page List に置かれる。
ARMアーキテクチャでのLinuxカーネル実装でも参照ビットのない状況に対処している。この場合、参照ビットも更新ビットもないプロセッサ本来のページテーブルと、それらのビットを含めたページテーブルを用意する。それらのビットのエミュレーションはページフォールト処理で行う。そして参照ビットをクリアしたとき、ページフォールトを確実に発生させなければならないので、プロセッサ本来のページテーブルを同時に書き換える。
ワーキングセットから除くページの選択は基本的にランダムになされるが、即座に書き換えられて使われるわけではないので、プロセスが再びそのページを参照し、ソフトフォールトを発生することもある。Modified Page List は二次記憶への書き戻しをまとめて行うことで効率を向上させ、書き戻したページは Free Page List に移される。Free Page List 上のページの順序は LRU か NRU に似た機構を提供し、全体として先述のセカンドチャンスと似たような効率となる。

ワーキングセット

プロセスのワーキングセットは、ある期間中にそのプロセスが使うと予想されるページの集合である。
「ワーキングセットモデル」は厳密にはページ置換アルゴリズムではなく、一種の中期スケジューラである。