学校では教えてくれないこと

キャッシュは諸刃の剣

2008.2

やあ、ビリーだよ。

コンピュータの世界で、処理を高速化するためのキャッシュという技術はみんなもなんとなく知ってるよね。キャッシュはいろいろなところに使われていて、アプリケーションは普通はキャッシュの存在を意識する必要はないんだ。

だけど、組込み用のプログラムでは、デバイスドライバのようなハードウェアを直接操作するプログラムを書く場合や、独自に開発したハードウェアにOSを移植する場合などは、キャッシュの存在を意識し、矛盾しないプログラムを書く必要があるんだ。

今回は、そんな「キャッシュ」について説明していくよ!

そもそも、キャッシュとは

キャッシュ(cache:英語)には、(食料や宝物などの)隠し場所、貯蔵場所という意味がある。コンピュータの世界でのキャッシュは、「食料や宝物」を「プログラムやデータ」に置き換えると、まさにそのとおりの意味。データの隠し場所、貯蔵場所という意味になるんだ。

組込みの世界で言う「キャッシュ」の説明の前に、わかりやすい例としてハードディスクのディスクキャッシュを例にとって、キャッシュの概念と効果を説明してみよう。

例えばPCショップなどで売られている、ハードディスクドライブ(HDD)を見てみよう。いまどきのHDDには、たいてい「キャッシュ 4MB」などの表記があって、HDDにキャッシュが内蔵されているようだね。ここで言うキャッシュとはキャッシュメモリのことで、HDDユニットにメモリが搭載されている、ということなんだ。

では、なぜキャッシュメモリを搭載するんだろう?その答えは、「見かけ上のディスクアクセスを高速化するため」なんだ。HDDは、内蔵された磁性体の円盤にデータを読み書きすることによってデータを保存する装置だけど円盤へ読み書きするには、円盤を回転させたり、ヘッドを移動させたりという機械的な動作が伴うため、同じ量のデータをメモリへ読み書きするのに比べてはるかに時間がかかるんだ。

これを高速化する方法として、CPUからHDDへデータを書き込むときは、円盤に直接書き込むんじゃなくて、HDD上のキャッシュメモリに書き込んで、HDDのコントローラがキャッシュメモリから円盤にデータを書き込む、ということを行うんだ。CPUからは、キャッシュメモリにデータを書き込んだ時点で、HDDにデータを書き終わったことになるから、次の処理を行うことができるんだ。

実際には、HDDのコントローラがキャッシュメモリから円盤にデータを書き終わった時点で処理が完了するから、 トータルの所要時間はキャッシュを介する場合と変わらないか、むしろキャッシュを介する分だけ余分にかかることになるんだけど、CPUから見るとHDDへ書き込みを行うために拘束される時間が少なくて済むよね。

つまり、キャッシュメモリを、CPUから円盤へデータを書き込む際の一時的な貯蔵場所として使うことで、「見かけ上高速になる」わけなんだ。

またCPUから見ると、HDDにキャッシュメモリが乗っているかどうかを知る必要はなく、HDD側が勝手に処理してくれるから、CPUから見ると、HDDのキャッシュメモリは「隠れた場所」ということも言えるね。

巨大な倉庫に書類をしまうことを考えるとよく分かるかもしれない。倉庫は巨大だから、しまう場所を探して棚の間を歩き回り、高い場所に上るためのはしごを用意していたら、とっても時間がかかってしまう。でも、入り口の近くに「隠し場所」があって、とりあえずここに放り込んでおけば、「倉庫にしまった」ことになるのなら、すぐに次の仕事にかかれるというわけだ。

キャッシュの説明図

きみの机の上の「未決箱」もキャッシュと言えるね。HDDから読み出す場合にも、同様のことが言えるよ。データを円盤から読み出すよりも、キャッシュメモリから読み出すほうが格段に速いから、パフォーマンスを上げることができる。でも、書き込みの時と違って、常にキャッシュが使用できるわけではないんだ。

このあたりの詳しいことは、次回で紹介しよう。

メモリにもキャッシュ?

いっぽう、Windows PCのスペック表などを見ると、L2キャッシュ512KB、L3キャッシュ2MBなどといった記述を見かけることがあるよね。Windows PC(Pentium系のCPU)でなくても、最近のCPUはキャッシュを使用するようになっていて、不可欠の機能になっているんだ。これらはメモリにつけられるキャッシュメモリだから、メモリキャッシュ、CPUキャッシュなどと呼ばれることもあるんだ。

理屈はさっきのHDDキャッシュと同じだけど、ここでいうキャッシュは、メモリアクセスを高速にするためのものなんだ。メモリアクセスを高速にするために、キャッシュメモリを使うとは??この謎を解くには、メモリの種類を理解する必要がある。

メモリの種類について語りだすと、それだけでひとつのシリーズぐらいの長さになってしまうから、今回は詳しく書かないけど、通常、コンピュータに搭載されているメインメモリ(主記憶)には、DRAMと呼ばれる種類のメモリが使われているんだ。

DRAMは、大容量のものが安価に製造できるんだけど、アクセス速度がCPUの動作クロックに比べて格段に遅い、という欠点がある。CPUが1つの命令を実行する単位を動作クロックといって、最近ではGHz単位となっているけど、DRAMのアクセス速度はこれよりも桁が1つぐらい落ちるんだ。

CPUがプログラムを実行する場合、次の命令をメモリから読み込んでその内容を実行する、ということを繰り返すんだけど、例えばCPUの動作クロックが1GHz(1命令を実行するのに1ナノ秒かかる)で、DRAMのアクセス速度が100MHz(1回読み出すのに10ナノ秒かかる)だとすると、CPUが次の命令をDRAMから読み出すのに10ナノ秒の時間がかかることになり、1命令を実行するのに11ナノ秒の時間が必要、ということになってしまう。

単純に計算すると、DRAMのアクセス速度がボトルネックになって、CPU の能力の1/11しか発揮できないということになってしまうよね(※注1)。

※注1
説明を簡単にするために単純な計算をしていますが、実際にはもう少し複雑な計算になります。実行するのにCPUの数クロックを必要とする命令があったり、DRAMも必ず動作クロックの時間で読み書きできるわけではありません。

いっぽう、SRAMという種類のメモリは、大容量化が難しく製造コストもDRAMより高いんだけど、アクセス速度はDRAMよりも高速なんだ。メインメモリを全てSRAMにできれば理想的だけど、非常に高価なシステムになってしまうよね。そこで、キャッシュの登場となるんだ。

つまり、遅いDRAMへアクセスするために、より高速なSRAMをキャッシュとして使用し、全体的にメモリアクセスを高速化して、メモリの大容量化と高速アクセスを両立させよう、というのがCPUキャッシュの考えなんだ。

最近のCPUは、最低限のキャッシュメモリをCPUに内蔵していて、より大きなキャッシュはCPUの外部に取り付けられる構造になっているものが増えてきているよ。CPUに一番近いところにあるキャッシュを一次キャッシュ(L1 キャッシュ)、さらにその外に二次(L2)、三次(L3)キャッシュを付けていく、という多段構成を取っているんだ。L1、L2、L3 の順にアクセス速度が速いけど、容量も少ない(より高価な SRAM)というのが一般的で、コストとパフォーマンスのバランスで各レベルのキャッシュの量が選択されているんだ。

組込みの世界で「キャッシュ」といえば、ふつうはこのCPUキャッシュを指すんだ。昔のシステムでは、CPUの動作クロックとDRAMの動作クロックに差はなかった(もしくは DRAMのほうが速かった)のでキャッシュは必要なかった。でも最近のCPUは動作クロックがGHzを超え、DRAMの動作速度がそれに追いついていないから、CPUの性能を引き出すためにはキャッシュが必要不可欠な存在になっていて、組込み用のCPUにも同じことが起こってきているんだ。

キャッシュが用意されているかどうか、あるとすればどのくらいのサイズかを見ると、逆にCPUの速さとそれに対する設計思想が分かると言えるね。CPUのカタログを見るときはこんな見方もしてみよう。

こうやって見てみると、いいことずくめのように見えるキャッシュだけど、キャッシュが存在することによる弊害、注意しなければならない点も存在するんだ。

それじゃあ、このキャッシュの動作をもう少し詳しく説明していこう。組込みシステムにおいてキャッシュがどのように関わってくるか、プログラムを組む上で何に気をつけないといけないか、を見ていくよ!

キャッシュの読み書き

キャッシュを搭載したシステムでは、メインメモリのデータは基本的にキャッシュを介して読み書きされる。基本的にということは、キャッシュを介さない読み書きもできるから、これについては後で説明するね。

データの書き込みは、前回話したようにまずキャッシュメモリに対して行われ、メインメモリにも同じデータが書き込まれるんだけど、この方式にも大きく分けて2種類あるんだ。キャッシュとメインメモリに同時に書き込む方法と、キャッシュだけに書き込み、メインメモリには後で書き込む方法だ。

前者はライトスルー(write through)、後者はライトバック、コピーバック(write back/copy back)などと呼ばれているよ。

ライトスルーは必ずメインメモリへの書き込みを伴うから、キャッシュが存在しない場合と同等、もしくはキャッシュのオーバーヘッドの分だけパフォーマンスが落ちる場合もあるんだ。しかし、メインメモリの内容は常に最新の状態に保たれる。キャッシュの恩恵が受けられるのは読み出しの時だけになってしまうけど、前回紹介した、メモリから命令を読み込む場合には有効だよ。

ライトバックはキャッシュにのみ書き込みが行われるから、高速に動作するんだ。書き込みの時にも、キャッシュの恩恵が受けられる仕組みだよ。その一方、メインメモリに書き込まれるタイミングが遅れるから、プログラムからはメモリに書き込みを行ったのに、実際には書き込まれていない、という状態が発生することになるんだ。この状態を、『キャッシュのコヒーレンシー(一貫性、coherency)が保たれていない、』と呼ぶ。組込みの世界のプログラムでは、こういったことにも気をつけておく必要があるよ。

CPUによって、ライトスルーしかサポートしていないもの、両方サポートしているものがあるんだ。またどのモードを使用するかは、ふつうはOSが起動時に設定するよ。データの読み出しは、読み出したいデータがキャッシュに存在するかどうかによって速度が大きく左右される。

例えば512MBのメインメモリに2MBのキャッシュメモリ、というように、キャッシュはメインメモリの全てをカバーするわけではないから、読み出したいデータが常にキャッシュに存在するかどうかは保証されているわけじゃないんだ。読み出したいデータがキャッシュに存在する状態を「キャッシュにヒットする」といって、この場合はメインメモリからではなくキャッシュメモリからデータが読み込まれるのだから、高速なアクセスが行われるんだ。

いかにヒット率を上げるか、ということがシステム全体のパフォーマンスに影響することになるね。そのためには、キャッシュメモリのサイズ自体を増やす他に、頻繁にアクセスされる領域は優先的にキャッシュに置いておく、といった工夫もされているんだよ。

キャッシュを使ってはいけない領域

キャッシュを利用するとパフォーマンスの向上が期待できて、欠点がないように思えるけど、キャッシュを使用することによって弊害が出るケースや、キャッシュを使用してはいけないケース、というのも存在するんだ。組込みプログラムを書くときには、これらのことも意識しておく必要があるよ。

(1) 周辺デバイスのレジスタ等

ここで言う周辺デバイスとは、RS-232CやLANやUSBコントローラなど、CPU以外のLSIとして実装されているものや、CPUに内蔵されているデバイスも指すよ。これらのレジスタは、キャッシュ禁止としておく必要があるんだ。

例えば、ある処理が完了したことをステータスレジスタの特定のビットを見て判断するようなプログラムがあったとしよう。このレジスタがキャッシュされ、かつキャッシュにヒットしている状態だと、キャッシュからレジスタ値が読み出されてしまうから、実際のレジスタのビットは更新されているにも関わらず、プログラムから見るといつまでたっても処理が完了しない、ということになってしまうんだ。

(2) DMA用バッファ

DMA(Direct Memory Access)とは、周辺デバイスがCPUを介さず直接メインメモリとの間でデータを転送する方法。LANやUSBなど、大量のデータを扱うデバイスでは、DMAを使用することが多くなるね。

ただし、CPUを介さない-キャッシュを介さない、ということになるんだよね。もし、DMAで使用する領域がCPUから見てキャッシュ可能エリアであった場合、周辺デバイスがDMAによりメモリ内容を更新したのにCPUは本来とは異なるキャッシュのデータを読んでしまう、という問題が発生してしまう。

ライトバックキャッシュを採用している場合は、CPUがキャッシュに書き込んだデータがメインメモリに反映されず、異なるデータがDMAでデバイスに転送されることにもなるよね。具体的には、USBメモリに書き込んだファイルの内容が化けたり、LANコントローラがデータの処理方法を誤認識して暴走する、といったことにもなってしまう事態が考えられるんだ。

このため、DMA用バッファは一般的にキャッシュを禁止しておくんだけど、キャッシュを生かしたままDMAを行う、ということができないわけではないんだ。この場合は、DMAを行うプログラム中(通常はデバイスドライバ)で キャッシュをコントロールする必要があるんだけど、CPUやハードウェアに対してかなり高度な知識が必要だから、詳しい方法はまたいずれ別の機会に紹介しようと思う。

(3) Dual Port RAM

通常RAMは、上記のDMAを除けばCPUからのみアクセスできるのだけど、CPU以外のデバイスや、複数のCPUからアクセスできるようになっているものがあるんだ。Dual Port RAMと呼ばれるもので、これもCPU以外から書き換えられる可能性があるから、キャッシュは禁止しておくべきなんだ。

今回話したことに注意しつつ、キャッシュの機能を最大限に活用するプログラムが書ければ、組込みソフトウェア開発者としては一人前だろうね!

このコラムを読んでるみんなの中には、4月から新入社員や後輩が入ってくる人も多いと思う。もしよかったら、後輩たちにもこのコラムを勧めてね。

ページのトップへ