自作OSで描画重視のプログラムを作っていると、本来スペックが高いはずの機種で思ったほど描画速度が出ない現象に遭遇することがあります。
memtest などでベンチマークを取ってみるとメモリの転送速度は十分に出ているはずなのに、実際に描画してみると単純な塗り潰しでさえ時間がかかっています。
なぜでしょうか?
おそらく犯人はキャッシュの設定です。
-
現代のCPUは技術の進歩によってものすごく高速に計算できますが、主要なメモリ素子であるDRAMは原理上あまり高速化できないのでCPUから見て遅いデバイスです。 そのためメモリアクセスはキャッシュが非常に重要になります。
キャッシュメモリは一度読み込んだメモリの内容を覚えておいて二度目の読み込みを高速化したり、書き込みを一定時間キャッシュメモリにため込んで実際の書き込みを遅延することでメモリアクセスによる速度低下を低減させるもので、さらに現代のCPUではDRAMとキャッシュの間の同期に必要な時間をCPUがメモリアクセスの順番を調整して隠蔽することでパフォーマンスを向上させています。
-
しかし、キャッシュに頼るとうまく動作しなくなるものがあります。 MMIO デバイスです。
MMIO デバイスはメモリ空間の一部を使用して通常のメモリアクセス命令でアクセスできるデバイスです。 MMIO デバイスの読み出しにキャッシュを経由すると、デバイスの状態が変化してもキャッシュの内容が更新されずに最新の状態が読み込めなかったり、コマンドを書き込んでもキャッシュによって書き込みが遅延されたり書き込みの順番が変わってコマンドがうまく実行できない問題が発生します。
そのような問題を避けるため、MMIO デバイスへのアクセスはキャッシュを無効化する必要があります。
-
ところで、ページテーブルにはキャッシュ制御の属性がありますが、自作OSを作るときに意識したことがあるでしょうか?
実は x86 CPU は非常に賢いので MMIO デバイスへのアクセスを自動的にキャッシュ無効化する機能があります。
この設定をしているのが MTRR (Memory-Type Range Registers) です。
MTRR は特定のメモリアドレス範囲のキャッシュ属性を設定できる MSR のレジスタ群で、通常は MMIO デバイスをキャッシュ無効にするように BIOS が設定します。
MTRR の設定とページ属性をCPUが総合的に判断してキャッシュ制御します。*1
-
さて、主題の描画の話に戻ります。
画面描画は実際には VRAM と呼ばれるメモリに書き込むことで行われます。 VRAM はディスプレイコントローラーが画面表示のために定期的に読み出す必要があるため、一般に CPU 側からのアクセスが制限された特殊なメモリになっていて、キャッシュの制御は WC (Write-Combining) が最も適しています。
WC は特殊なモードなので多くの機種ではデフォルトで設定されていません。
x86 のページ属性には WC が存在せず、 PAT で WC を設定しても手元の環境ではうまく動作しませんでした。 追記:PAT がうまく動作しなかったのはおそらく起動時のページング初期化処理のバグです。
ということで MTRR の設定を調整します。
#
MTRR はデフォルトのタイプと特定の範囲のタイプを指定することができます。
Intel ではデフォルトを UC (Uncacheable) 、実際にメモリが搭載されてる範囲に WB (WriteBack) 設定するのを推奨しています。 この設定になっている場合、 VRAM の範囲に WC 設定を追加するだけで見違えたように描画が高速になります。
しかし、 Intel の推奨と逆にデフォルトを WB 、 MMIO デバイスの範囲を UC に設定になっている機種も多く存在します。 例えば、いくつかの Intel チップでは C000_0000〜FFFF_FFFF の間に VRAM が存在してまとめて UC 設定になっていることがあります。 この範囲には重要な MMIO デバイスの APIC や HPET などが存在するので MTRR を分割して追加する必要があります。
しかし、 MTRR はエントリ数に上限があり、範囲指定はビットマスクによって行うため、すでに設定されてる範囲から特定の範囲を除外した設定をうまく指定できないことがあります。
特定の機種用に MTRR を調整することは可能ですが、全ての機種に汎用的に設定する万能な方法はありません。
いかがでしたか?
自作 OS で画面描画が思ったより遅い場合、 MTRR で VRAM を WC に設定すると驚くほど高速化することができます。
実際に簡単な描画ベンチマークで WC 設定前は2桁FPSしか出なかったものが WC 設定するだけで 500 FPS 以上出るようになります。
しかし、 MTRR の変更が困難な機種もあり、 PAT を使った方がより柔軟に設定できます。
*1:なお、現代のCPUはMTRRとページ属性の間にさらにPATというものが挟まっています。