¿Estrategias de mejora de rendimiento para VM / intérprete?
He escrito una VM simple en C, usando un simple cambio de instrucciones, sin ninguna instrucción que decodifique, pero el rendimiento es terrible.
Para operaciones aritméticas simples, la máquina virtual es aproximadamente 4000 veces más lenta que el código C nativo para las mismas operaciones. Probé con un grupo de arreglos de 10 millones de longitud, el primero que consta de las instrucciones del programa, operaciones aleatorias + - *, 2 arreglos que contienen enteros aleatorios y el tercer arreglo es el almacenamiento de destino de la operación.
Esperaba ver una caída de 3 a 4 veces en el rendimiento aritmético, por lo que '4000x realmente me impresionó. Incluso los lenguajes interpretados más lentos parecen ofrecer un mayor rendimiento. Entonces, ¿en qué me estoy equivocando con mi enfoque y cómo puedo mejorar el rendimiento sin recurrir a la compilación JIT al código de máquina?
La implementación es ... básicamente la más sencilla que se me ocurre:
begin:
{
switch (*(op+(c++)))
{
case 0:
add(in1+c, in2+c, out+c); goto begin;
case 1:
sub(in1+c, in2+c, out+c); goto begin;
case 2:
mul(in1+c, in2+c, out+c); goto begin;
case 3:
div(in1+c, in2+c, out+c); goto begin;
case 4:
cout << "end of program" << endl;
goto end;
default:
cout << "ERROR!!!" << endl;
}
}
end:
ACTUALIZACIÓN: Estaba jugando con la duración del programa cuando noté que el QElapsedTimer que estaba usando para perfilar estaba realmente dañado. Ahora estoy usando la función clock () y, de acuerdo con ello, el goto calculado se está ejecutando a la par del código nativo, tal vez un poco más bajo. ¿Es legítimo ese resultado? Aquí está la fuente completa (es feo, lo sé, es solo para probar después de todo):
#include <QtGlobal>
#include <iostream>
#include <stdio.h>
#include <ctime>
using namespace std;
#define LENGTH 70000000
void add(int & a, int & b, int & r) {r = a * b;}
void sub(int & a, int & b, int & r) {r = a - b;}
void mul(int & a, int & b, int & r) {r = a * b;}
void div(int & a, int & b, int & r) {r = a / b;}
int main()
{
char * op = new char[LENGTH];
int * in1 = new int[LENGTH];
int * in2 = new int[LENGTH];
int * out = new int[LENGTH];
for (int i = 0; i < LENGTH; ++i)
{
*(op+i) = i % 4;
*(in1+i) = qrand();
*(in2+i) = qrand()+1;
}
*(op+LENGTH-1) = 4; // end of program
long long sClock, fClock;
unsigned int c = 0;
sClock = clock();
cout << "Program begins" << endl;
static void* table[] = {
&&do_add,
&&do_sub,
&&do_mul,
&&do_div,
&&do_end,
&&do_err,
&&do_fin};
#define jump() goto *table[op[c++]]
jump();
do_add:
add(in1[c], in2[c], out[c]); jump();
do_sub:
sub(in1[c], in2[c], out[c]); jump();
do_mul:
mul(in1[c], in2[c], out[c]); jump();
do_div:
div(in1[c], in2[c], out[c]); jump();
do_end:
cout << "end of program" << endl; goto *table[6];
do_err:
cout << "ERROR!!!" << endl; goto *table[6];
do_fin:
fClock = clock();
cout << fClock - sClock << endl;
delete [] op;
delete [] in1;
delete [] in2;
delete [] out;
in1 = new int[LENGTH];
in2 = new int[LENGTH];
out = new int[LENGTH];
for (int i = 0; i < LENGTH; ++i)
{
*(in1+i) = qrand();
*(in2+i) = qrand()+1;
}
cout << "Native begins" << endl;
sClock = clock();
for (int i = 0; i < LENGTH; i += 4)
{
*(out+i) = *(in1+i) + *(in2+i);
*(out+i+1) = *(in1+i+1) - *(in2+i+1);
*(out+i+2) = *(in1+i+2) * *(in2+i+2);
*(out+i+3) = *(in1+i+3) / *(in2+i+3);
}
fClock = clock();
cout << fClock - sClock << endl;
delete [] in1;
delete [] in2;
delete [] out;
return 0;
}