デコードとディスパッチの解釈とスレッド化された解釈でプログラムを実行する際の実際的な違いを理解しようとしています。
両方の例が本当に役立ちます。
Javaバイトコードがどのように機能し、アセンブリ言語がどのように機能するかを理解しています。しかし、DDIとTIはどこに適合しますか?
デコードとディスパッチの解釈とスレッド化された解釈でプログラムを実行する際の実際的な違いを理解しようとしています。
両方の例が本当に役立ちます。
Javaバイトコードがどのように機能し、アセンブリ言語がどのように機能するかを理解しています。しかし、DDIとTIはどこに適合しますか?
(注:「デコードしてディスパッチする」とは、スイッチベースのインタープリターを意味すると想定します。)
実行時のスイッチベースのインタープリターとスレッド化されたインタープリターの違いは、基本的に、実行されるジャンプの数です。
スイッチベースのインタプリタでは、命令は中央の場所でデコードされ、デコードの結果に基づいて、デコードされた命令を処理するコードにジャンプが実行されます。そのコードが命令の解釈を終了すると、集中型のデコードコードに戻り、次の命令に進みます。これは、解釈された命令ごとに(少なくとも)2つのジャンプが実行されることを意味します。次のCコードは、そのようなインタープリターがどのように見えるかを示しています。
typedef enum {
add, /* ... */
} instruction_t;
void interpret() {
static instruction_t program[] = { add /* ... */ };
instruction_t* pc = program;
int* sp = ...; /* stack pointer */
for (;;) {
switch (*pc++) {
case add:
sp[1] += sp[0];
sp++;
break;
/* ... other instructions */
}
}
}
スレッド化されたインタープリターでは、デコードコードは一元化されませんが、命令を処理する各コードの最後に複製されます。これは、命令が解釈されると、一元化されたデコードコードに戻る代わりに、インタプリタが次の命令をデコードしてすぐにジャンプすることを意味します。ANSI-Cでスレッドコードを効率的に実装することは実際には不可能ですが、GCCの「計算されたgoto」拡張機能はそのために非常にうまく機能します。以前のインタプリタのスレッドバージョンは次のとおりです。
void interpret() {
void* program[] = { &&l_add, /* ... */ };
int* sp = ...;
void** pc = program;
goto **pc; /* jump to first instruction */
l_add:
sp[1] += sp[0];
++sp;
goto **(++pc); /* jump to next instruction */
/* ... other instructions */
}
ジャンプを保存することは別として、そのようなスレッド化されたインタープリターは、複製された間接ジャンプ(次の命令への)が最新のCPUによってより適切に予測できるため、より効率的です。Anton Ertlのホームページには、特に「効率的な通訳者の構造とパフォーマンス」と呼ばれる興味深い論文がいくつかあり、そこから上記のコードが採用されています。