Domanda

Ok, prima, non voglio alcun tipo di flamewar qui o qualcosa del genere. La mia domanda più grande è più teorica e includerà pochi esempi.

Quindi, come ho scritto, non riesco a capire come può essere interpretato il linguaggio anche poco efficiente. E dal momento che è moderno, prenderò Java come esempio.

Torniamo ai giorni in cui non c'erano compilatori JIT. Java ha la sua macchina virtuale che è fondamentalmente il suo hardware. Scrivi codice, piuttosto che compilato in bytecode per aver toccato almeno un po 'di lavoro dalla macchina virtuale, va bene. Ma considerando quanto sia complesso anche il set di istruzioni RISC può essere nell'hardware, non riesco nemmeno a pensare al modo di farlo presso Hardware emulato dal software.

Non ho esperienza nella scrittura di macchine virtuali, quindi non so come sia fatto a livello più efficiente, ma non riesco a pensare a nulla di più efficace del test di ogni istruzione per l'ADN corrispondente rispetto alle azioni appropriate. Sai, qualcosa come: if(instruction=="something") { (do it) } else if(instruction=="something_diffrent"){ (do it) }eccetera....

Ma questo deve essere terribilmente lento. Eppure, anche ci sono articoli che Java era lento prima dei compilatori JIT, dicono ancora che non è così lento. Ma per emulare deve richiedere molti cicli di clock di HW reale per eseguire un'istruzione bytecode.

Eppure, anche intere piattaforme si basano su Java. Ad esempio, Android. E i primi Verisons di Android non avevano il compilatore JIT. Sono stati interpretati. Ma non dovrebbe essere Android terribilmente lento? Eppure non lo è. Lo so, quando chiami una funzione API, dalla libreria Android, sono scritte nel codice macchina, quindi sono efficienti, quindi questo aiuta molto.

Ma immagina di scrivere il tuo motore di gioco da Sratch, usando API solo per la visualizzazione di immagini. Dovresti eseguire molte operazioni di copia dell'array, molti calcoli che sarebbero terribilmente lenti quando emulati.

E ora alcuni esempi come ho promesso. Da quando lavoro principalmente con MCU, ho trovato JVM per Atmel AVR MCU. Thay afferma che l'MCU 8MHz può fare 20k Optcodes Java al secondo. Ma poiché l'AVR può fare la maggior parte delle istruzioni in uno o due cicli, diciamo che 6000000 istruzioni sono la media. Questo ci dà che JVM senza compilatore JIT è 300 volte più lento al codice macchina. Allora perché diventare Java così popolare senza il compilatore JIT? Non è una cattiva perdita di prestazioni? Non riesco proprio a capirlo. Grazie.

È stato utile?

Soluzione

Abbiamo in giro il codice byte per molto tempo. Sulla vecchia Apple II, il sistema P USCD era molto popolare, che ha compilato Pascal in byte, che sarebbe stato interpretato da un 6502 a 8 bit che potrebbe essere in esecuzione a 2 MHz. Quei programmi hanno funzionato ragionevolmente velocemente.

Un interprete bytecode si baserebbe generalmente su una tabella di salto piuttosto che su una catena di if/then/else dichiarazioni. In C o C ++, ciò implicherebbe a switch dichiarazione. Fondamentalmente, l'interprete avrebbe l'equivalente di una matrice di codice di elaborazione e userebbe l'opportunità nell'istruzione del codice byte come indice dell'array.

È anche possibile avere un codice di byte che è di livello superiore rispetto alle istruzioni della macchina, in modo che l'istruzione di un codice byte si traducesse in diverse istruzioni del codice macchina a volte numerose. Un codice byte che è stato costruito per un linguaggio particolare può farlo abbastanza facilmente, poiché deve solo abbinare il controllo e le strutture di dati di quel particolare linguaggio. Ciò allunga le spese di interpretazione e rende l'interprete più efficiente.

È probabile che una lingua interpretata abbia una penalità di velocità rispetto a una lingua compilata, ma questo è spesso poco importante. Molti programmi elaborano input e output a velocità umana e che lascia un'enorme quantità di prestazioni che possono essere sprecate. Anche un programma rilegato in rete avrà probabilmente una potenza CPU molto più disponibile di quanto non sia necessaria. Esistono programmi che possono utilizzare tutta l'efficienza della CPU che possono ottenere e per ovvie ragioni tendono a non essere scritte in lingue interpretate.

E, naturalmente, c'è la questione di cosa si ottiene per una certa inefficienza che potrebbe o meno fare la differenza. Le implementazioni di lingua interpretate tendono ad essere più facili da portare rispetto alle implementazioni compilate e il codice di byte effettivo è spesso portatile. Può essere più facile inserire funzionalità di livello superiore nella lingua. Consente alla fase di compilazione di essere molto più breve, il che significa che l'esecuzione può iniziare molto più velocemente. Potrebbe consentire una diagnostica migliore se qualcosa va storto.

Altri suggerimenti

Ma non dovrebbe quindi essere Android terribilmente lento?

Definire "terribilmente lento". È un telefono. Deve elaborare "comporre la prima cifra" prima di comporre la seconda cifra.

In qualsiasi applicazione interattiva, il fattore limitante è sempre il tempo di reazione umano. Potrebbe essere 100 volte più lento ed essere ancora più veloce dell'utente.

Quindi, per rispondere alla tua domanda, sì, gli interpreti sono lenti, ma di solito sono abbastanza veloci, in particolare perché l'hardware continua a diventare più veloce.

Ricorda quando è stato introdotto Java, è stato venduto come un linguaggio di applet web (in sostituzione e ora sostituito da JavaScript --- che ha anche interpretato). Fu solo dopo la compilazione JIT che divenne popolare sui server.

Gli interpreti di bytecode possono essere più veloci di una linea di if () utilizzando una tabella di salto:

 void (*jmp_tbl)[256] = ...;  /* array of function pointers */
 byte op = *program_counter++;
 jmp_tbl[op]();

Esistono due modi diversi per affrontare questa domanda.

(i) "Perché va bene eseguire codice lento"

Come James ha già accennato sopra, a volte la velocità di esecuzione non è tutto ciò che sei interessato. Per molte app che funzionano in modalità interpretata possono essere "abbastanza veloci". Devi tenere conto di come verrà utilizzato il codice che stai scrivendo.

(ii) "Perché viene interpretato il codice inneficante"

Esistono molti modi in cui puoi implementare un interprete. Nella tua domanda parli sull'approccio più ingenuo: fondamentalmente un grande switch, interpretando ogni istruzione JVM mentre si legge.

Ma puoi ottimizzarlo: ad esempio, invece di guardare una singola istruzione JVM, puoi vederne una sequenza e cercare modelli per i quali hai interpretazioni più efficienti disponibili. JVM di Sun in realtà fa alcune di queste ottimizzazioni nell'interprete stesso. In un lavoro precedente, un ragazzo ha impiegato del tempo per ottimizzare l'interprete in quel modo e interpretato da Java Bytecode era notevolmente più veloce dopo i suoi cambiamenti.

Ma nei moderni JVM che contengono un compilatore JIT, l'interprete è solo un trampolino di lancio fino a quando il JIT non fa il suo lavoro, quindi le persone non trascorrono molto tempo a ottimizzare l'interprete.

12 MHz sarebbe un attiny, che è un microprocessore a 8 bit. Ciò significa (ad esempio) che un'istruzione nativa 'Aggiungi "può aggiungere solo due numeri a 8 bit per ottenere un risultato a 9 bit. Il JVM è fondamentalmente un processore virtuale a 32 bit. Ciò significa che le sue istruzioni aggiuntive aggiungono due 32- Numeri di bit insieme per produrre un risultato a 33 bit.

Pertanto, quando si confrontano i tassi di istruzione, dovresti aspettarti una riduzione 4: 1 del tasso di istruzioni come minimo assoluto. In realtà, sebbene sia facile simulare un'aggiunta a 32 bit con 4 aggiunte a 8 bit (con carry), alcune cose non si ridimensionano in quel modo. Solo per esempio, secondo Atmel's Nota dell'app, una moltiplicazione 16x16 che produce un risultato a 32 bit viene eseguita in ~ 218 cicli di clock. La stessa nota app mostra una divisione a 16/16 bit (che produce un risultato a 8 bit) in esecuzione in 255 cicli.

Supponendo che quelle scala linearmente, possiamo aspettarci che le versioni a 32 bit della moltiplicazione prendano ~ 425-450 cicli di clock e i cicli di divisione ~ 510. In realtà, dovremmo probabilmente aspettarci un po 'di spese generali, il che ridurrebbe ancora di più la velocità, aggiungendo almeno il 10% a tali stime, probabilmente le rende più realistiche.

In conclusione: quando si confrontano le mele con le mele, diventa evidente che gran parte della differenza di velocità che stai parlando non è affatto reale (o non è comunque attribuibile il sovraccarico JVM).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top