火曜日, 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命令で書き換えられるので、配列や可変アドレスでも大丈夫です。この命令があることで、あらゆる命令のパラメータを、事実上の汎用レジスタとして利用することが可能になります。

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

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

(つづく)

金曜日, 1月 18, 2013

マルチプロセッサとメモリアクセス

近年、ノートPCや携帯機器までマルチコアが普通に載るようになってきて、今ではスマートフォンにまでクアッドコア(4CPU)が登場するようになりました。だったらもっと載せればいいじゃないという話になりそうですが、実際はそう簡単にはいきません。

世の中のプログラムは必ずしも並列化できない(アムダールの法則)というのもありますが、もし並列化できたとしても、メモリアクセスの限界(フォン・ノイマン・ボトルネック)で、どうしても性能が抑えられてしまいます。CPUが計算するためには、まずメモリからプログラムとデータを読んで、計算結果をメモリに書き込む必要があります。CPUだけがどんなに速くても、メモリアクセスが間に合わなければ、全く意味がないわけです。

では、現在のマルチプロセッサはどのようにしてこの問題に対処しているのでしょうか?

cache_memory.png

現在、世の中に出回っている大部分のマルチコアプロセッサが、基本的にはこのような分散キャッシュメモリを持つ構成になります。この構成では各プロセッサは必要となるプログラムやデータの一部分をキャッシュメモリにコピーして使うので、共有メモリへのアクセスは格段に減ります。マルチコアプロセッサでは通常、キャッシュメモリと共有バスは外部の共有メモリよりも遥かに高速で動作しますので、ちゃんとプロセッサが増えただけの性能を発揮することができます。

キャッシュメモリの容量はプロセッサの性能に直結するので、今やプロセッサの中で最も電力と面積を必要とする部品となっています。キャッシュメモリは元々、外部メモリとプロセッサ内部の速度差を埋める役割を果たしていたのですが、マルチコアになるとキャッシュ一貫性によりメモリの同時アクセスのために欠かせない機構となりました。でも、メモリを内蔵する構成のプロセッサにおいては、当然ながら無用の長物です。

では、メモリを内蔵する小規模なマルチプロセッサは、どのような構成にすればよいでしょうか? まず考えられるのは、力技です。

shared_memory.png

マルチポートメモリを利用することにより、共有メモリとプロセッサを直結することが理論的には可能です。ですが、面積効率が悪く(ポート数の2乗に比例するといわれている)、またFPGAでは利用できないため(大抵は2ポートまで)、この方式は却下となります。

もう一つ考えられるのは共有メモリを諦めてプロセッサ毎に専用のメモリを持たせ、代わりに通信用のバスを用意する方式です。ハードウェアの設計が楽なのでこの構成は比較的よく見られ、例えばPS3Cellプロセッサはこれに近い構造となっています。

local_memory.png

これはハードウェア設計は楽なのですが、ソフトウェアの方は大変です。特に小規模の場合、ただでさえ少ないメインメモリが、プロセッサ毎に分割されてしまうのは論外といえます。無理やり他のプロセッサのメモリをアクセスする構成も考えられなくはないですが、プロセッサに負荷が掛かる分、複雑な割に効率が悪そうです。

共有メモリの分割をプロセッサ毎ではなく、アドレス領域に応じたメモリバンク分割にすることにより、また違った構成が考えられます。

crossbar_switch.png

クロスバースイッチにより、複数のプロセッサが別々のメモリバンクにアクセスする限りにおいて、同時アクセスが可能となります。ただし、複数のプロセッサが同一のバンクにアクセスしようとした場合には、一つを除いて他のアクセスがブロックされます。プロセッサが増えるとクロスバー機構が急激に肥大化するのが問題ですが、それをうまく簡略化するための研究が昔から盛んなようです。(例えばこことか)

もしここで、アドレスをプロセッサ数で割った余りでメモリバンク分割すると、面白いことが起きます。例えば上の図でメモリバンク0~3を、アドレスを4で割った余りが0から3の領域のメモリとします。もしある時点で4つのプロセッサの実行アドレスが100, 201, 302, 403とすると、それぞれ4で割った余りは0, 1, 2, 3となるので同時に別々のメモリから命令読み出しアクセスができます。次の時点では実行アドレスが101, 202, 303, 404となるので余りは1, 2, 3, 0となり、また同時アクセスが可能です。こうして、ジャンプ命令を実行しない限り、4つのプロセッサは同時アクセス可能な状態を維持し続けます。

この状況で、プロセッサ自体を回転させることができれば、クロスバー機構がまったく要らなくなります。回転といってもIC内部で物理的に回転させるわけにはいかないので、実際には全ての内部状態を信号としてつないで転送することになります。

cyclic_smp.png

プロセッサの内部状態というと、当然全てのレジスタの値も含まれますので、レジスタがたくさんある場合にはそれも全部隣に送らないといけません。つまりこの場合、レジスタが少なければ少ないほど有利なわけです。

ここで、レジスタレスアーキテクチャによる小規模マルチプロセッサの可能性が浮かび上がってきます。

(つづく)

火曜日, 1月 15, 2013

レジスタレスアーキテクチャとは

レジスタレスアーキテクチャとは、文字通りレジスタを省略したマイクロプロセッサアーキテクチャです。

とはいえ、レジスタが全く無いプロセッサでは何もできないので、実際には必要最小限のレジスタを持ち、さらにメインメモリを汎用レジスタとしても利用できるプロセッサということになります。

RISC出現以前の古典的なプロセッサでは、こうしたメインメモリをレジスタの代わりとして利用できる形式のものがよく見られました。その中でも特に有名なのは、Apple II初代ファミコンで使われた8ビットCPUの6502です。6502はプロセッサ内部のレジスタが少ない代わりに、メインメモリの0番地から255番地までの256バイトの領域を、ゼロページとして事実上汎用レジスタのように利用可能であるという特徴がありました。この当時はCPUクロックも1~2MHz程度で、プロセッサ内部のレジスタと外部のメインメモリの速度差があまりないことから、こういった設計もアリでした。

現在の高性能プロセッサは内部ではGHz単位で動作しますので、外部メモリとの速度差を埋めるために大量のレジスタやキャッシュメモリが欠かせません。しかし、FPGAをメインメモリとプロセッサが同居するマイクロコントローラとして利用する場合には、少し事情が変わってきます。この構成では外部にメモリを接続しなくてもよい代わりに、貴重な内部メモリ資源をレジスタやメインメモリに振り分けなければならないため、あまり大量のレジスタを持つとそれだけメインメモリを圧迫してしまうことになります。そして、レジスタとメインメモリは結局同じ内部メモリを使うことになるので、レジスタによるメインメモリアクセスの削減は、もともとあまり意味がないわけです。

ということで、FPGA内部の埋め込みメモリを、全てメインメモリとして利用可能であるのが、このアーキテクチャの第一の意義であるといえます。

そして、このアーキテクチャのもう一つの重要な意義は、マルチコア化にあります。マルチコアとはマルチプロセッサの一形態で、大まかにいえば、一つのICパッケージ内に複数のプロセッサを詰め込んだものです。昔はICの実装密度が低く、プロセッサが別々のパッケージに分かれているのが普通でした。しかし、プロセッサの信号は本数が多い上に高速で、その分基板設計は大変でした。ICの実装密度が上がるにつれてマルチコア化が可能となり、コンパクトな上に基板設計が楽なので、今ではほとんどのマルチプロセッサがマルチコアとなりました。

高速な信号を基板上に出した途端、素人にはとても手の出せないものになりますが、FPGA内部であれば、せいぜい設計ツールとの格闘でなんとかなります。マルチコアプロセッサとメインメモリを同一のFPGA内に収めれば、あとはハードウェア記述言語で書ける世界なので、これが素人がマルチコアを設計するための、現状ではほぼ唯一の手段となります。ただし、そのためには一つのFPGAパッケージ内に収まるだけの、(常識的にはありえない)コンパクトな回路でマルチコアプロセッサを実現する必要があります。レジスタレスアーキテクチャは、そのための設計論でもあります。

(つづく)