2011-04-11 16 views
5

Il mio progetto ha una VM che esegue un codice byte compilato da un linguaggio specifico del dominio. Sto osservando i modi in cui posso migliorare il tempo di esecuzione del codice byte. Come primo passo vorrei vedere se c'è un modo per migliorare semplicemente l'interprete byte-code prima di entrare nella compilazione del codice macchina.Ciclo interprete virtual machine/byte code ottimale

Il ciclo principale dell'interprete si presenta così:

while(true) 
{ 
    uint8_t cmd = *code++; 
    switch(cmd) 
    { 
    case op_1: ...; break; 
    ... 
    } 
} 

DOMANDA: C'è un modo più veloce per implementare questo ciclo senza ricorrere a Assembler?

L'unica opzione che vedo è GCC specifica per utilizzare goto dinamico con indirizzi di etichette. Piuttosto che un break alla fine di ogni caso, potrei passare direttamente all'istruzione successiva. Speravo che l'ottimizzatore avrebbe fatto questo per me, ma a quanto pare lo smontaggio non lo fa: c'è un salto costante ripetuto alla fine della maggior parte dei op_codes.

Se rilevante, la macchina virtuale è una macchina basata su registri semplice con registri in virgola mobile e interi (8 di ciascuno). Non c'è stack, solo un heap globale (quella lingua non è così complicata).

risposta

3

Uno molto semplice ottimizzazione è che invece di switch/case/caso/caso/caso/caso,

solo definire un array con puntatori a funzione (dove ogni funzione dovrebbe elaborare un comando specifico, o una coppia di comandi nel qual caso è possibile impostare diverse voci nella matrice per la stessa funzione, e la funzione stessa potrebbe verificare il codice esatto), invece di

switch(cmd) 

basta fare un

array[cmd]() 

Questo è dato che non si dispone di troppi comandi. Inoltre, fai qualche controllo se non definirai tutti i comandi possibili (forse hai solo 300 comandi, ma devi usare 2 byte per rappresentarli, quindi invece di definire un array con 65536 elementi, controlla se il comando è meno di 301 e se non lo è, non effettuare la ricerca)

Se non lo farai, ordina almeno i comandi che sono quelli più utilizzati all'inizio dell'istruzione switch.

Altrimenti sarebbe esaminare gli hashtable, ma presumo che non si abbiano molti comandi, e in tal caso l'overhead di eseguire una funzione di hash probabilmente ti costerà di più che non dover fare un passaggio. (Oppure hai una MOLTO funzione di hash semplice)

+1

È altamente improbabile che le chiamate di funzione anziché lo switch siano più veloci (le chiamate di funzione hanno un sovraccarico significativo). Lo switch dovrebbe comunque essere compatibile con un jump-table (GCC lo fa almeno). –

+0

Cool, non sapevo che su gcc, ma in ogni caso, si potevano definire funzioni come inline, e invece di passare qualsiasi argomento le rendeva variabili globali e così via, rendendo la funzione chiamata overhead molto piccola. Ovviamente, quando si arriva a questo livello è probabilmente più facile codificare tutto in assembler. (Se si sa come farlo.) – Cray

+1

Non è possibile effettuare chiamate in linea effettuate tramite una tabella di invio. L'opzione più vicina qui è quella di utilizzare gli indirizzi delle etichette GCC, che in pratica consentono di creare le proprie funzioni inline in una tabella di salto. –

1

Qual è l'architettura? Potresti ottenere un'accelerazione con gli opcode con allineamento delle parole, ma questo farà esplodere le dimensioni del tuo codice, il che significa che dovrai bilanciarlo con il costo di una perdita della cache.

+0

x86_64. Questo sicuramente gonfierà le dimensioni del mio codice, ma il codice è piuttosto piccolo. –

+0

In secondo luogo, questo potrebbe non funzionare poiché il codice è intercalato con dati in linea (costanti). Per mantenere l'allineamento tutto dovrebbe crescere fino a 8 byte. Sarebbe un bel costo (ci proverò comunque). –

+0

No, aumento della velocità zero. –

-1

Pochi evidente ottimizzazione vedo sono,

  1. Se non si utilizza cmd da nessuna parte che switch() quindi, utilizzare direttamente il riferimento indiretto puntatore, switch(*code++). Per un ciclo più lungo while(true), questo può essere poco utile.
  2. In switch(), è possibile utilizzare continue anziché break.Perché quando continue viene utilizzato all'interno di if/else o switch, il compilatore sa che l'esecuzione deve passare al ciclo esterno; lo stesso non è vero per break (rispetto a switch). Spero che questo aiuti.
+0

Punto 1 Ho provato prima e non fa alcuna differenza (l'ottimizzatore probabilmente lo vede lo stesso). Punto 2 Effettivamente, sembra fare una leggera differenza (strano che l'ottimizzatore non lo faccia da solo, ma la differenza di tempo è così piccola che è difficile escludere un'anomalia statistica). –