火曜日, 1月 22, 2013

アキュムレータマシンと自己書き換え

レジスタレスアーキテクチャによるマルチプロセッサは、ちょうどこんな風に、プログラムがプロセッサの上をぐるぐる追いかけっこしながら実行するイメージになります。

smp_image.png

プログラムがプロセッサ間を移動するためにはできるだけ身軽な方が良いので、なるべくレジスタの少ないアーキテクチャを採用することになります。こういったアーキテクチャの代表格に、アキュムレータマシンがあります。アキュムレータマシンは、例えば以下の様な形式の命令を実行します。

ADD メモリアドレス

これは実際には、唯一の汎用レジスタであるアキュムレータAccの値に、メモリアドレスで指定されたメモリの内容を足す命令になります。

Acc ← Acc + (メモリの内容)

アキュムレータマシンは回路が非常にコンパクトになる反面、メモリアクセスが多くなるため高速化にはあまり向きません。例えば上の「ADD メモリアドレス」の場合、まずADD命令とメモリアドレスの読み出しで最低1回、そしてメモリ内容の読み出しでもう1回、最低でも計2回のメモリアクセスが必要となります。

現在のプロセッサは、なるべくメモリアクセスを減らす方向に進化を続けていますので、それに反するアキュムレータマシンは古典的なものとしてほとんど忘れ去られてしまいました。しかし、この弱点を除けば、小規模マルチプロセッサの要素として望ましい性質を備えています。そこで、アキュムレータマシンを改良して、小規模マルチプロセッサに組み込むことを考えます。

もしアキュムレータ以外にレジスタがあれば、メモリアドレスの代わりにレジスタ番号を指定して

ADD レジスタ番号 { Acc ← Acc + レジスタ }

とすることで、メモリアクセスの1つをレジスタ読み出しに置き換えることができます。それでも、レジスタ読み出しにはまずレジスタ番号が確定する必要があるので、命令読み出しと同時には実行できません。実際には、以下の4つの段階に分けて命令が実行されます。

  1. 命令読み出し
  2. レジスタ読み出し
  3. 足し算の計算
  4. 結果の書き込み

現在多くのプロセッサは、これらの段階をパイプライン処理で同時に実行して、高性能をたたき出しています。しかし、実際にこの方法で性能を発揮させるには様々な工夫が必要であり、現在のプロセッサの複雑化の一因となっています。

結局、レジスタを使ったとしても、命令実行のためのパラメータ(足し算の場合は+の左辺と右辺)が命令読み出しと同時に確定しない限り、こういった問題はどうしても避けられません。手立てはただ一つ、命令のパラメータを固定値(イミディエイト/即値)に限定することです。足し算命令の場合はこうなります。

ADD 固定値 { Acc ← Acc + 固定値 }

命令とパラメータを同時に読み出せれば、メモリアクセスは1回で済みます。その後すぐに足し算ができますので、基本的に複雑なパイプラインは不要となります。もちろん、パラメータが固定値だけではプロセッサとして使い物になりませんが、これについてはコードの書き換えを用いることで解決が可能です。具体的には、パラメータで指定されたアドレスにある命令のパラメータを、現在のアキュムレータの値に書き換えるPUTという命令を用意します。

PUT 命令アドレス { [命令アドレス] ← Acc }

PUT命令自体の命令アドレス指定も他のPUT命令で書き換えられるので、配列や可変アドレスでも大丈夫です。この命令があることで、あらゆる命令のパラメータを、事実上の汎用レジスタとして利用することが可能になります。

コードの自己書き換えは、現在のプロセッサでは特別な場合を除いてほとんど使われないテクニックです。セキュリティやメモリ保護の問題もありますが、何よりもキャッシュメモリやパイプライン処理において、コード書き換えの処理が非常に効率が悪いというのが理由だと考えられます。このように現在ではすっかり主流から外れてしまった自己書き換えという手法を、レジスタレスアーキテクチャでは積極的に利用します。

まとめると、レジスタレスアーキテクチャは、アキュムレータマシンと自己書き換えという、現在主流から外れてしまったテクニックを、小規模マルチプロセッサの要素として復活させたものになります。

(つづく)