本連載はHisa Ando氏による連載「コンピュータアーキテクチャ」の初掲載(2005年9月20日掲載)から第72回(2007年3月31日掲載)までの原稿を再掲載したものとなります。第73回以降、最新のものにつきましては、コチラにて、ご確認ください。

では、プリフェッチは良いことばかりかというと、副作用もある。プリフェッチを行って新たなキャッシュラインを読み込むには、現在、キャッシュに入っているキャッシュラインを追い出す必要がある。プリフェッチが当たっており、すぐあとにそのデータが使われれば良いが、無駄なプリフェッチで必要なデータを含むキャッシュラインが追い出されてしまったのでは逆効果である。

容量の少ない1次キャッシュでは、このような問題が起こりやすいので、1次キャッシュまでデータを持ってくるプリフェッチ命令だけでなく、2次キャッシュまで持ってくるだけというプリフェッチ命令をサポートするプロセサも存在する。この場合は、データを使うときに1次キャッシュのミスは発生するが、2次キャッシュまで行けばヒットするので、メモリまでアクセスに行くのと比べれば大幅に短い時間でデータが使用可能になる。従って、このようなプロセサでは、確実性の高いプリフェッチは1次キャッシュまでもってくるプリフェッチ命令を使い、そうでないものは2次キャッシュまでのプリフェッチを使えば効果的である。

また、いくら2次キャッシュが大きいとは言え、大量のプリフェッチを出すと無駄なメモリアクセスが発生し、本来のメモリアクセスがそれに妨げられて遅くなってしまったり、消費電力も増えるという副作用が発生するので、確実性の低いプリフェッチは、ほどほどに使うのが良い。

メモリはどう管理されるのか?

ここでちょっとキャッシュから離れて、メモリ管理について述べる。プログラムは、命令やデータは一定のメモリアドレスにあることを想定しており、空いていればどのアドレスにロードしても良いという訳ではない。従って、複数のプログラムを動作させるためには、各プログラムにはそれぞれに独自の仮想空間を提供し、仮想空間とメモリの実空間を対応付けるメカニズムが必要となる。8086のような初期のプロセサでは、各プログラムに対して仮想空間のアドレスに下駄を履かせ、例えば仮想空間の0番地を実空間では10000番地から開始し、大きさは2000バイトの領域を割り当てるという、オフセットとサイズを指定するセグメント方式がとられた。この例ではプログラムが認識する番地に10000を足した実アドレスでメモリをアクセスし、アドレスが12000を超えると割り当ての範囲外ということでエラーを通知する。

しかし、ご存知のように、Cプログラムではメモリをmallocで割り当て、不要になるとfreeで返すという処理がよく使われる。また、Javaなどでもstackやheapをガンガン使い、ダイナミックにメモリの使用、返却が繰り返される。また、プロセスの生成に伴いメモリを割り当て、終了すると開放するというように、メモリはダイナミックに割り当て、開放される。このようなメモリの使い方をすると、メモリ空間の使用、不使用領域が細切れで存在することになってしまい、新しくメモリ領域を獲得しようとすると、空き領域の合計は十分でも、オフセットとサイズで確保できるような連続した空き領域は残っていないという不都合も生じる。

これに対応するため、現在のプロセサではページという単位でメモリを管理するのが一般的である。ページの大きさはIntel IA32プロセサでは4KBであり、4~8KB程度が一般的であるが、データベースや科学技術計算などで大きなメモリ空間を扱う場合は沢山のページを管理する必要がでて効率が悪いので、ラージページとしてMB単位の大きなページを含めて何種類かのページサイズをサポートするプロセサが増加している。

ページ単位のメモリ管理は、各プログラムが使用する仮想空間をページ単位に分割し、各ページ単位に実メモリのどのアドレスに対応するかの情報を記憶するページテーブルを使う。なお、処理を簡単にするために、仮想空間、実空間ともにページの開始番地はページサイズの整数倍とするのが普通である。

そしてプログラムからのメモリアクセスに対しては、このページテーブルに従って仮想アドレスを物理アドレスに変換してメモリをアクセスする。このようなやり方をすることにより、物理アドレスは飛び飛びでも仮想アドレス空間では連続に見せることが出来、メモリを有効に使うことができる。

しかし、プログラムが256MBの仮想空間を使うときに8KB単位のページとすると、テーブルのサイズは32Kエントリ必要であり、かつ、並行に実行されるプログラム分のテーブルが必要なので、テーブルのためのメモリが大量に必要となってしまう。このため、プロセサの中に収容することは出来ず、ページテーブル本体はメモリ上に置かれ、OSが管理することになる。しかし、OSも含めて各プログラムのメモリアクセスの度にメモリ上のページテーブルを参照するのではオーバーヘッドが大きく、全く性能が出ない。