Pergunta

Estou tentando entender a diferença prática durante a execução de um programa em Decodificar e despachar interpretação e interpretação encadeada.

Exemplo de ambos realmente ajudará.

Eu entendo como o Java ByteCode funciona e como uma linguagem de montagem funciona. Mas onde o DDI e o TI se encaixam?

Contexto: Máquinas virtuais: plataformas versáteis para sistemas e processos

Foi útil?

Solução

(Nota: vou assumir que, por "decodificar e despachar", você quer dizer um intérprete baseado em switch.)

A diferença entre um intérprete baseado em switch e um rosqueado no tempo de execução é, basicamente, o número de saltos que são executados.

Em um intérprete baseado em comutador, as instruções são decodificadas em algum local central e, com base no resultado da decodificação, um salto é executado no pedaço de código que lida com a instrução decodificada. Depois que esse código terminar de interpretar a instrução, ele volta ao código de decodificação centralizado, que prossegue com a próxima instrução. Isso significa que (pelo menos) dois saltos são realizados por instrução interpretada. A parte seguinte do código C ilustra como pode ser um intérprete:

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 */
    }
  }
}

Em um intérprete rosqueado, o código de decodificação não é centralizado, mas duplicado no final de cada pedaço de código que lida com uma instrução. Isso significa que, uma vez interpretada uma instrução, em vez de voltar para algum código de decodificação centralizado, o intérprete decodifica a próxima instrução e imediatamente salta para ele. A implementação do código rosqueado com eficiência no ANSI-C não é realmente possível, mas a extensão "computada" do GCC funciona muito bem para isso. Aqui está uma versão rosqueada do intérprete anterior:

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 */
}

Além de salvar um salto, esses intérpretes roscados também são mais eficientes porque o salto indireto replicado (para a próxima instrução) pode ser previsto melhor pelas CPUs modernas. Anton Ertl tem alguns papéis interessantes sobre sua página inicial, especialmente aquele chamado "a estrutura e o desempenho de intérpretes eficientes", dos quais as peças de código acima foram adaptadas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top