私は、仮想マシンがどのように機能するかを学ぶために、単純なバイトコードインタープリターに取り組んでいます。 VMについて読んだことがありますが、それらはすべてスタックベースまたはレジスタベースのいずれかであるようです。当時は理にかなっていたのですが、通訳の仕事を始めて、自分が何をしているのか気づきました。 (私はレジスターマシンに決めました。)これらすべての値をポインターからこれらの偽のレジスターにロードし、操作を実行してから、結果をレジスターに戻し、ポインターにコピーします。直接やってみたらどうだろうと思いました。オーバーヘッドとして機能するだけの偽のレジスタを作成する理由。 VMについてはまだ十分に理解していないかもしれませんが、私のやり方ははるかに速いように思えます。
VMの仕事は、任意のコードを実行することです。 (JITの考えはさておき、純粋なVMについて話しているだけです)。 VMのバイトコードまで任意のプログラムをどのようにコンパイルする予定ですか?命令セットが必要です。その命令セットは何かに取り組む必要があります。高水準言語から変数を直接操作することについて話している場合、それが可能であることは確かですが、実際にはVMではありません。ただの通訳です。インタプリタはシンボリックにすることができ(つまり、名前を処理することができます)、高レベルにすることができます。 ASTノードまたは純粋なテキストで動作できます。ただし、VMは一般に低レベルであり、単純なCPUをエミュレートし、バイトコード自体は中間言語の形式です。したがって、プログラムをバイトコード形式で配布して、ある程度の保護を提供できます(ただし、Javaおよび.NETの世界では、VMのレベルは少し低くなります)。
高レベルのインタープリターの欠点は、プログラムを実行するたびに多くのシンボリックな作業を行うことです。 VMの強みは、コンパイラーと組み合わせると得られます。 C#を例にとってみましょう。確かに、インタープリターC#へのプログラムを高レベルで作成できます(私はこれをParrot VM年前に作成しました)が、変数のコンパイルなどの作業を事前に行うと、実行がはるかに効率的になります。レジスタまたはスタックオペランドに。そうすると、プログラムはASTよりもはるかに密度が高くなり、密度の高いバイトコードとして便利に表されます。
ジャストインタイムコンパイルとも呼ばれる、JITterによって実際のネイティブ実行可能ファイルにオンザフライでコンパイルされる、より単純な、または実際の命令に近い仮想操作を定義します。 opsが単純で低レベルであるほど、JITterの実装は効率的です。
また、非JITモード(純粋なVM操作)では、シンボリックアクセスをレジスタまたはスタックオペランドにコンパイルすると、コアレジスタ領域がキャッシュ可能であることを確認することでVMのパフォーマンスを向上させることができます。 。変数がヒープ全体にあり、それらを直接操作することを選択した場合、キャッシュの効率が低下します。レジスタVMを使用すると、固定レジスタまたは無制限の仮想レジスタを使用して設計できますが、いずれの場合も、アロケータはそれらをRAMの連続するセグメントにパックされた固定レジスタにマップします。より少ないL1キャッシュラインに適合し、空間的局所性のパフォーマンスを向上させます。
ただし、変数を直接操作する高レベルのシンボリックVMは可能です。ここにいくつかの選択肢があります。
VM A-シンボリック:
newglobal "count"
add "count", 1
VM B-レジスタ(メモリ転送):
mkglobal "count"
move $R1, "count"
inc $R1
VM Bの利点は、オペコードが実際のライブプロセッサに非常に近いか、簡単に変換できることです。ほとんどのプロセッサはメモリ変数の操作を許可せず、変数をレジスタまたはスタックにロードする必要があるためです。 JITはやることがはるかに少なくなります。 VM Aの場合、add
操作は実際には太い集約操作であり、個々のネイティブ操作にコンパイルする必要があります。
2つのアプローチを簡単に比較するには、Perl5とC#/。NETバイトコードを調べてください。 Perl5をバイトコードにコンパイルすることは可能ですが、それはデフォルトの形式ではありません。 Perlは、実際にはメモリ内ASTのよりシンボリックな解釈を行います。そのため、Perl6用のParrotを作成したとき、それは大きなアーキテクチャの変更でしたが、その言語にとって今のところ正しい決定であるとは確信していません。
調査したい仮想マシンの3番目のタイプがあります。このタイプは、静的単一代入と呼ばれる実行モデルを使用します。これは、レジスタとスタックを廃止し、1回計算されてから1回以上使用される結果のより抽象的な概念を優先しますが、バイトコードはそれがどのように格納されるかについては何も示していません。 。
このアイデアの最もよく知られている実装はLLVMであるため、それらがどのように実行したかを確認することをお勧めします。