Rodrigo Kumpera Weblog

Meus achados sobre tecnologia

A Nova onda de interpretadores

September 30th, 2008 · 2 Comments

Interpretadores voltaram a ser um assunto muito discutido devido aos resultados alcansados pela SquirrelFish (interpretador de javascript do Webkit) e ao fato da VM do Android também usar um. Curiosamente, ambas VMs são baseadas em registradores em vez de pilha, como as máquinas virtuais tradicionais como JVM e CLR.

A grande diferença está na forma como valores intermediários são calculados. Em uma VM baseada em pilha, operandos são adicionados ao topo da pilha enquanto operadores os retiram e realizam alguma operação. Uma máquina baseada em registradores usa de variaveis para armazenar resultados intermediários.

Para exemplificar melhor, o código “a = b * c + 10″ ficaria algo como:

Máquina de pilha:

push_local 'b'
push_local 'c'
mul
push_const 10
add
store_local 'a'

Máquina de registradores:

load_local R0 <= 'b'
load_local R1 <= 'c'
mul R2 <= R0, R1
load_const R3, 10
add R4 <= R2, R3
store_local 'a' <= R4

A primeira vista a diferença é quase que estética, porém se considerarmos quanto espaço precisamos para representar o código e o número de operações de leitura e escrita à memória cada uma faz, vamos notar como são técnicas fundamentalmente diferentes.

Vamos assumir um formato bem simples para representar o código de ambas, que é um byte para designar a operação seguido de um byte para cada operando. Aplicando esta forma, temos.

Máquina de pilha:

push_local 'b' //2 bytes
push_local 'c' //2 bytes
mul //1 byte
push_const 10 //2 bytes
add //1 byte
store_local 'a' //2 bytes

Total: 10 bytes

Máquina de registradores:

load_local R0 <= 'b' //3 bytes
load_local R1 <= 'c' //3 bytes
mul R2 <= R0, R1 //4 bytes
load_const R3 <= 10 //3 bytes
add R4 <= R2, R3 //4 bytes
store_local 'a' <= R4 //3 bytes

Total: 20 bytes

Fica claro aqui que uma máquina de pilha admite uma representação muito mais compacta do mesmo programa. Em geral essa relação não é tão gritante, pois existe uma série de formas de diminuir o número de instruções necessárias para uma máquina de registradores.

Agora vamos analisar quantas operações de memória cada máquina faz, verificando cada instrução individualmente.

Maquina de pilha:

push_local X

  1. lê a variavel local X
  2. lê o endereço atual do topo da pilha
  3. escreve X no topo da pilha
  4. incrementa e armazena o novo valor para topo da pilha

mul / add

  1. lê o endereço atual do topo da pilha
  2. lê o valor do topo da pilha
  3. lê o valor abaixo do topo da pilha
  4. escreve o resultado no local abaixo do topo da pilha
  5. decrementa e armazena o novo valor para topo da pilha

push_const X

  1. lê o endereço atual do topo da pilha
  2. escreve X no topo da pilha
  3. incrementa e armazena o novo valor para topo da pilha

store_local X

  1. lê o endereço atual do topo da pilha
  2. lê o valor no topo da pilha
  3. escreve o valor em X
  4. decrementa e armazena o novo valor para topo da pilha

Aplicando esses valores ao programa em questão temos um total de 25 operações.

Máquina de registradores:

load_local X <= Y

  1. lê o valor da variavel local Y
  2. escreve o valor no registrador X

mul / add X <= Y, Z

  1. lê o valor do registrador local Y
  2. lê o valor do registrador local Z
  3. escreve o valor no registrador X

load_const X <= Y

  1. escreve o valor de Y no registrador X

store_local X <= Y

  1. lê o valor do registrador Y
  2. escreve o valor no registrador X

Fazendo a conta, temos um total de 13 operações.

Ironicamente neste caso, uma máquina de registradores leva uma significante vantagem em relação a uma máquina de pilha. Apesar de se tratar de uma simplificação, na prática o resultado é o mesmo. Em máquinas modernas onde o custo de acessar a memória principal é muito grande, uma máquina de registradores tem o potencial de ser mais rápida.

Essas dicotomia entre qual admite uma representação mais compacta e qual permite uma implementação mais eficiente é fundamental na arquitetura de qualquer máquina virtual. Apesar disso, existe muito além da simples forma de funcionar do interpretador que define a real performance da VM, ainda mais se falarmos de linguagens dinâmicas. Em um artigo futuro pretendo mostrar uma implementação de exemplo de ambos os tipos de máquinas virtuais.

Tags: Performance · Programming · language design

2 responses so far ↓

  • 1 Wagner Elias - Nova onda de browsers e as máquinas virtuais // Sep 30, 2008 at 5:00 pm

    [...] as principais mudanças está a transformação de javascript direto para linguagem de máquina. Este post do Rodrigo Kumpera mostra uma outra característica, agora os interpretadores são baseados em registradores e não em [...]

  • 2 felipe tonello // Feb 18, 2009 at 5:23 pm

    Cara, muito bom!

    Eu tenho muito interesse em compiladores e interpretadores. Fiz um interpretador de expressões matemáticas usando pilhas, para brincar. Não sabia que existia essa técnica de usar registros.

    Continue com seus artigos, são ótimos, apesar de não concordar quando você crítica *tanto* C ou C++ =P Eu sei disso pois acompanho na ccppbrasil hehe

    Abraços

Leave a Comment