Интерпретация декодирования и отправки и потоковая интерпретация
Я пытаюсь понять практическую разницу во время выполнения программы в декодировании и диспетчерской интерпретации и потоковой интерпретации.
Пример обоих действительно поможет.
Я понимаю, как работает байт-код Java и как работает язык ассемблера. Но где подходят DDI и TI?
Контекст: виртуальные машины: универсальные платформы для систем и процессов
1 ответ
(Примечание: я предполагаю, что под "декодированием и отправкой" вы подразумеваете интерпретатор на основе коммутатора.)
Разница между основанным на коммутаторе и многопоточным интерпретатором во время выполнения состоит, по сути, в количестве выполненных переходов.
В основанном на коммутаторе интерпретаторе инструкции декодируются в некотором центральном местоположении, и на основе результата декодирования выполняется переход к фрагменту кода, который обрабатывает декодированную инструкцию. Как только этот фрагмент кода завершил интерпретацию инструкции, он возвращается к централизованному коду декодирования, который переходит к следующей инструкции. Это означает, что (по крайней мере) два прыжка выполняются за интерпретированную инструкцию. Следующий фрагмент кода на 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 "computed 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 */
}
Помимо сохранения перехода, такие многопоточные интерпретаторы также более эффективны, поскольку реплицированный косвенный переход (к следующей инструкции) может быть лучше предсказан современными процессорами. У Антона Эртла есть несколько интересных статей на его домашней странице, особенно под названием "Структура и производительность эффективных переводчиков", из которых были адаптированы вышеуказанные фрагменты кода.