Java仮想マシンが中間データ値を保持するレジスタなしで設計されたのはなぜですか?代わりに、すべてがスタック上で機能します。レジスタの代わりにスタックベースのアーキテクチャを持つことの特定の利点はありますか?
3 に答える
私は前の答えに敬意を表して反対しなければなりません。
式スタックの存在の仮定は、レジスターの存在よりも優れた仮定ではありません。通常、レジスタマシンはスタックオペコードを直接実行できず、スタックマシンはレジスタオペコードを直接実行できません。それらはマッピングする必要があります。
EJPは、「ホストマシンにスタックがある場合、すべての場合と同様に、何もする必要はありません」と述べています。これは虚偽の陳述です。すべてのマシンに計算を実行できるスタックがあることを提案すると、スタックマシンが実際に何であるかについて混乱が生じます。
- スタックベースのマシンには、「式スタック」の最上位に暗黙のオペランドを持つ命令セットがあります。汎用スタックではありません。汎用スタックは、スタックマシンが作成するものではありません。値を保存/復元できます。すべてのプラットフォームにそれがあります。しかし、彼らは計算を実行することはできません。
- レジスタベースのマシンは、命令ストリームにエンコードされた明示的な仮想レジスタであるオペランドを使用して、一般的なオペコードを実行します。これらのVMには、レジスタを保存および復元するための汎用スタックとオペコードがまだあります。スタック計算操作をデータスタックにマップすることはできません。コア命令セットのオペランドは、レジスタ、メモリアドレス、またはイミディエート(オペコード内にエンコード)です。
したがって、あるマシンアーキテクチャのオペコードを別のマシンアーキテクチャにマッピングすることは、確かに「何もすること」以上のものがあります。ネイティブJavaオペコードアクセラレーションを備えたチップを使用している場合を除いて、それを想定しない方がよいでしょう。
スタックマシンが移植性のための良い選択であるという点については議論しません。スタックからレジスタへ、またはレジスタからスタックへ命令をマップするための「やるべきことがある」と言っています。両方とも可能です。
しかし、多くの話は学術的です。実際には、2014年に普及しているプラットフォームは登録です。スタックベースの「プロセッサ」でさえ、多くの場合、レジスタチップの上に実装されたソフトチップです。Java Card 3の仕様を参照し、実装を確認して、実際に使用されているプロセッサを確認してください。それは演習として残しておきます。
非常に特殊なプラットフォーム用のVMを設計している場合(GreenSpacesプロセッサでのみ実行されるJVMを設計するように依頼された場合など)を除き、コンテキストは一般的なアプリケーションであると想定します。 、組み込みアプリケーション、セットトップボックス、ファームウェアコントローラ、おもちゃ、さらにはスマートカード。これらすべてについて、ARMのような8〜32ビットプロセッサが使用されているか、利用可能であることがわかります。主流のJVMはCで記述されています。Cを使用すると、仮想スタックまたは仮想レジスタのオペコードを使用してインタプリタを設計できます。90年代には、JVMオペコードを直接サポートするスタックベースのJavaプロセッサについて多くの議論がありました。2014年には、レジスタベースのハードウェアでこれらの実装が見られます。
一般的なアプリケーションの場合、スタックベースのVMは実際にはスタックにマップされません。これらのマシンはすべて、レジスタベースのプライマリ命令を使用します。スタック操作は、レジスタまたはコールスタックを保存および復元するためのものであり、計算、ロジック、または分岐を行うものではありません。スタックVMは、スタックまたはレジスタ命令セットであるかどうかに関係なく、マシンのネイティブ命令セットにJITします。Hennessy And Pattersonによると、1980年以降、事実上すべての新しいプロセッサアーキテクチャが登録されています-「コンピュータアーキテクチャの定量的アプローチ」 。これは、レジスタレジスタ、レジスタメモリ、またはレジスタイミディエート命令セットを意味します。スタックベースの追加がないマシンでは、スタックに2つの値を追加することはできません。x86では、ADD操作のスタックベースのオペコードは次のように変換される可能性があります。
push a
push b
add
ネイティブレジスタコードへ:
mov eax, [addra]
mov ebx, [addrb]
add eax, ebx
次に、オペコードストリームがスタックであるかレジスタであるかに関係なく、JITはそれをネイティブコードにコンパイルできます。したがって、VMモデルの選択は単なるソフトウェアモデルです。レジスターマシンも同様に仮想です。これらはネイティブレジスタ情報をエンコードせず、オペコードのレジスタは仮想シンボルです。
Javaが設計されたとき、考えは小さなオペコードと8ビットプロセッサの互換性に向けられていました。スタックベースのオペコードは、レジスタオペコードよりも小さいです。だからそれは理にかなっています。ジェームズ・ゴスリングがオーク(Javaの元の名前)にそれを選んだ主な理由の1つは、単純さだけでなく、どこかで読んだと思います。私はただ手を参照していません。その点で、私はPéterTörökに同意します。
- オペコードをスタックすることには、目に見える利点があります。多くの場合、コードストリームは小さく/密度が高くなります。JVMとCLRに関しては、観測されたスタックベースのバイトコードは他のマシンよりも15〜20%小さい可能性があります。スタックバイトコードは、8ビット未満で簡単にエンコードできます。(Forthマシンは、わずか20個程度のオペコードを持つことができます)。opstreamは、エンコード/デコードが簡単です。Java用にアセンブラまたは逆アセンブラを作成してからx86用に作成してみてください。
- オペコードを登録することには、目に見える利点があります。式をエンコードするオペコードが少ない=IPCが優れている=インタプリタでの高レベルの分岐が少ない。また、少数のレジスタ(8〜16)を事実上すべての最新のプロセッサに直接マップすることもできます。レジスタを使用すると、参照のキャッシュの局所性が向上するため、スループットが大幅に向上します。それどころか、スタックマシンは多くのメモリ帯域幅を使用します。
VMでは、レジスタバイトコードは多くの場合、大きな仮想レジスタセットを使用するため、より大きなバイトコードが必要になります。ほとんどの実際のハードウェアでは、レジスタは通常、約3ビット(Intel x86)から5ビット(Sparc)でエンコードされるため、密度はVMからVM、CPUからCPU、またはVMからCPUで異なる場合があります。Dalvikはレジスタを表すために4〜16ビットを使用しますが、Parrotはすべてのオペコード(少なくとも私が使用したv2バイトコード形式)でレジスタごとに8ビットを使用しました。Dalvikはより良い平均密度を達成します。それらを構築した私の経験に基づくと、プリミティブに厳密に固執して小さなレジスタファイルを使用しない限り、8ビットバイトコード内で汎用レジスタマシンを構築することは困難です。これは直感的ではないように思われるかもしれませんが、通常、単一のオペコードには、実際には異なるレジスタタイプの複数のエンコーディングがあります。
最後のポイント:JITが登場すると、ソフトコアの最適化の多くがウィンドウの外に出る可能性があります。
スタックマシンがハードウェアによりよくマッピングされるという議論で私が取る主な例外は、JVMが実行されている場所やテクノロジーが向かっている場所を無視することです。Chuck Moore以外では、スタックベースのプロセッサ(IGNITEおよびGreenSpaces GA144)を設計している人は誰も知りません。ほとんどの新しい開発はレジスタマシンです。スタックマシンの議論は主に学術的なものです。8ビットスタックプロセッサの引数ごとに、Cコンパイラを備えたいくつかのレジスタマシン(レジスタ付きのHitachi H8、レジスタ付きのARM926、Intel 8051)を紹介できます。Javaよりも純粋なスタックプロセッサでForthに書き込む可能性が高い場合があります。新しいプラットフォームの場合、Cコンパイラや完全なJVMなどがある安価なARMプロセッサを使用する方が理にかなっています。これらはレジスタ命令セットを実行します。
- ARM926 / ARM926EJ-S- http: //www.arm.com/products/processors/classic/arm9/arm926.php
- H8- http://en.wikipedia.org/wiki/H8_Family
それで、それが本当なら?それは重要ですか?私の経験に基づく私の意見は、「人々が考えるほどではない」というものです。バイトコードは単なる中間表現であることを忘れないでください。マシンの純粋に解釈されたコアは、多くの場合、踏み石、ブリッジ、デフォルトのフェイルセーフコアです。最終的な目的地は、ネイティブパフォーマンスへのJITterを備えた最終的なバージョン2です。したがって、これを1、2回行った多くの人が抱く見解は、コアを可能な限り単純に保ち、最適化の90%をそこで費やしてJITに移行することが理にかなっているということです。インターペットコアを調整するための無駄な作業は、時期尚早の最適化と見なされる可能性があります。一方、JITterを計画していない場合、またはメモリの制約のためにJITが実用的でない場合は、仮想コアで最適化するか、VMをチップに実装します。
Javaは、ゼロから移植できるように設計されています。しかし、バイトコードを実行しているプラットフォームに存在する特定のレジスタに依存している場合、どのようにしてバイトコードを移植可能に保つことができますか?特に、元々は主流のPCとは非常に異なるプロセッサアーキテクチャを持つセットトップボックスで実行することを目的としていたことを考慮に入れてください。
JVMが実際に使用可能なレジスタやその他のハードウェア固有のものを知っているのはランタイムだけです。次に、JITコンパイラーは、必要に応じてこれらを最適化できます(そして最適化する予定です)。
仮想マシンを設計する場合、仮想レジスタのセットよりもスタックを配置する方がはるかに簡単です。ホストマシンにスタックがある場合は、すべての場合と同様に、何もする必要はありません。レジスタがある場合でも、一時的なものなど、他の用途に使用できます。一方、ホストマシンにレジスタがない場合は、 、スタックのみで、レジスタを中心にVMを設計する場合、仮想スタックとしての使用を妨げる方法でネイティブスタックを使用する必要があるため、実装上の大きな問題が発生します。