Los intérpretes volvieron a ser un tema muy discutido debido a los resultados alcanzados por SquirrelFish (intérprete de JavaScript del Webkit) y el hecho de que la VM de Android también lo usa. Curiosamente, ambas VM se basan registros en lugar pilas, tales como las VM tradicionales como JVM y CLR.

La gran diferencia está en cómo los valores intermedios se calculan. En una VM basada en pila, los operandos se añaden al tope de la pila, mientras que los operadores los retiran y realizan alguna operación. Una máquina basada en registros usa variables para almacenar resultados intermedios.

Para ilustrar mejor, el código "a = b * c + 10" sería algo así como:

Máquina de pila:

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

Máquina de registros:

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 primera vista, la diferencia es casi estética, pero si se tiene en cuenta la cantidad de espacio que necesitamos para representar el código y el número de operaciones de lectura y escritura a la memoria que cada uno hace, observamos cómo las técnicas son fundamentalmente diferentes.

Supongamos un formato muy simple para representar el código de ambos, que es un byte para describir la operación seguida por un byte para cada operando. Aplicando esta forma, tenemos.

Máquina de pila:

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 registros:

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

Es evidente que una máquina de pila admite una representación mucho más compacta del mismo programa. En general esta relación no es tan sorprendente, porque existe una serie de formas de reducir el número de instrucciones necesarias para una máquina de registros.

Ahora vamos a analizar cuantas operaciones de memoria cada máquina hace, verificando cada instrucción individualmente.

Máquina de pila:

push_local X

1. lee la variable local X
2. lee la dirección actual de la parte superior de la pila
3. X escribe en la parte superior de la pila
4. incrementa y almacena nuevo valor en la parte superior de la pila

mul / add

1. lee la dirección actual de la parte superior de la pila
2. lee el valor de la parte superior de la pila
3. lee el valor por debajo de la parte superior de la pila
4. escribe el resultado en lugar debajo de la parte superior de la pila
5. decrementa y almacena el nuevo valor en la parte superior de la pila

push_const X

1. lee la dirección actual de la parte superior de la pila
2. escribe X en la parte superior de la pila
3. incrementa y almacena el nuevo valor en la parte superior de la pila

store_local X

1. lee la dirección actual de la parte superior de la pila
2. lee el valor en la parte superior de la pila
3. escribe el valor en X
4. decrementa y almacena el nuevo valor en la parte superior de la pila

Aplicando estos valores al programa en cuestión tiene un total de 25 operaciones.

La máquina de registros:

load_local X <= Y

1. lee el valor de la variable Y
2. escribe el valor en el registro X

mul / add X <= Y, Z

1. lee el valor del registro local Y
2. lee el valor del registro local Z
3. escribe el valor en registro X

load_const X <= Y

1. escribe el valor de Y en el registro X

store_local X <= Y

1. lee el valor del registro Y
2. escribe el valor en el registro X

Haciendo la cuenta, tenemos un total de 13 operaciones.

Irónicamente, en este caso, una máquina de registros tiene una ventaja significativa sobre una máquina de pila. Aunque esta es una simplificación, en la práctica el resultado es el mismo. En máquinas modernas, donde el costo de acceso a la memoria principal es muy grande, una máquina de registros tiene el potencial de ser más rápida.

Esta dicotomía entre cual admite una representación más compacta, y cual permite una implementación más efectiva es crucial en la arquitectura de cualquier VM. Sin embargo, hay mucho más de la simple manera de operar del intérprete, que define el rendimiento real de la VM, aún más si hablamos de lenguajes dinámicos.

Traducción libre de Kumpera

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw