Esempio di codice:
(setq x 60)
(setq y 40)
(+ x y)
esecuzione con un interprete Lisp
In un Lisp basato interprete di cui sopra sarebbe di dati Lisp e l'interprete guarda ogni forma e corre il valutatore. Dal momento che è in esecuzione delle strutture dati Lisp, lo farà ogni volta quando si vede sopra il codice
- ottenere la prima forma
- abbiamo un'espressione
- si tratta di un setq forma speciale
- valutazione 60, il risultato è di 60
- cercare il luogo per la variabile x
- impostare la variabile x al 60
- ottenere la forma successiva ... ...
- abbiamo una chiamata di funzione per +
- valutare x -> 60
- valutare y -> 40
- chiamata la funzione + con 60 e 40 -> 100 ...
Ora +
è un pezzo di codice che in realtà scopre cosa fare. Tipicamente Lisp ha diversi tipi di numeri e (quasi) nessun processore ha supporto per tutti quelli: fixnums, bignum, rapporti, complessi, float, ... Quindi la funzione +
ha bisogno di scoprire quali tipi hanno gli argomenti e cosa può fare per aggiungili.
esecuzione con un compilatore Lisp
Un compilatore semplicemente emettere codice macchina, che farà le operazioni. Il codice macchina farà tutto ciò che fa l'interprete: controllare le variabili, controllare i tipi, controllare il numero di argomenti, chiamare le funzioni, ...
Se si esegue il codice macchina, è molto più veloce, poiché il Lisp le espressioni non devono essere guardate e interpretate. L'interprete dovrebbe decodificare ogni espressione. Il compilatore ha già fatto questo.
È ancora più lento di un codice C, poiché il compilatore non conosce necessariamente i tipi e emette semplicemente il codice completamente sicuro e flessibile.
Quindi questo codice Lisp compilato è molto più veloce dell'interprete che esegue il codice Lisp originale.
L'utilizzo di un compilatore Lisp ottimizzare
A volte non è abbastanza veloce. Quindi hai bisogno di un compilatore migliore e dì al compilatore Lisp che dovrebbe mettere più lavoro nella compilazione e creare codice ottimizzato.
Il compilatore Lisp potrebbe conoscere i tipi di argomenti e variabili. È quindi possibile indicare al compilatore di omettere i controlli di runtime.Il compilatore può anche supporre che +
sia sempre la stessa operazione. Quindi può inline il codice necessario. Dal momento che conosce i tipi, può solo generare il codice per questi tipi: l'aggiunta di interi.
Tuttavia, la semantica di Lisp è diversa dalla C o dalla macchina. A +
non si tratta solo di vari tipi di numeri, ma passerà automaticamente da piccoli numeri interi (fixnum) a interi interi (bignum) o errori di segnale su overflow per alcuni tipi. Si può anche dire al compilatore di ometterlo e basta usare una aggiunta intera nativa. Quindi il tuo codice sarà più veloce, ma non così sicuro e flessibile come il codice normale.
Questo è un esempio per il codice completamente ottimizzato, utilizzando l'implementazione 64 bit di LispWorks. Utilizza le dichiarazioni di tipo , dichiarazioni in linea e direttive di ottimizzazione. Si vede che dobbiamo dire al compilatore un po ':
(defun foo-opt (x y)
(declare (optimize (speed 3) (safety 0) (debug 0) (fixnum-safety 0))
(inline +))
(declare (fixnum x y))
(the fixnum (+ x y)))
Il codice (64bit codice macchina Intel) allora è molto piccolo e ottimizzato per quello che abbiamo detto al compilatore:
0: 4157 push r15
2: 55 push rbp
3: 4889E5 moveq rbp, rsp
6: 4989DF moveq r15, rbx
9: 4803FE addq rdi, rsi
12: B901000000 move ecx, 1
17: 4889EC moveq rsp, rbp
20: 5D pop rbp
21: 415F pop r15
23: C3 ret
24: 90 nop
25: 90 nop
26: 90 nop
27: 90 nop
Ma tenere a mente al di sopra del codice fa qualcosa di diverso da ciò che l'interprete o il codice di sicurezza avrebbero fatto:
- calcola solo fixnums
- lo fa in silenzio ove rflow
- il risultato è anche un Fixnum
- lo fa senza il controllo degli errori
- non funziona per altri tipi di dati numerici
Ora il codice non ottimizzato:
0: 49396275 cmpq [r10+75], rsp
4: 7741 ja L2
6: 4883F902 cmpq rcx, 2
10: 753B jne L2
12: 4157 push r15
14: 55 push rbp
15: 4889E5 moveq rbp, rsp
18: 4989DF moveq r15, rbx
21: 4989F9 moveq r9, rdi
24: 4C0BCE orq r9, rsi
27: 41F6C107 testb r9b, 7
31: 7517 jne L1
33: 4989F9 moveq r9, rdi
36: 4C03CE addq r9, rsi
39: 700F jo L1
41: B901000000 move ecx, 1
46: 4C89CF moveq rdi, r9
49: 4889EC moveq rsp, rbp
52: 5D pop rbp
53: 415F pop r15
55: C3 ret
L1: 56: 4889EC moveq rsp, rbp
59: 5D pop rbp
60: 415F pop r15
62: 498B9E070E0000 moveq rbx, [r14+E07] ; SYSTEM::*%+$ANY-CODE
69: FFE3 jmp rbx
L2: 71: 41FFA6E7020000 jmp [r14+2E7] ; SYSTEM::*%WRONG-NUMBER-OF-ARGUMENTS-STUB
...
Si può vedere che chiama una routine di libreria per fare l'aggiunta. Questo codice dovrebbe fare tutto l'Interprete. Ma non ha bisogno di interpretare il codice sorgente Lisp. È già compilato con le istruzioni della macchina corrispondenti.
Perché il codice Lisp compilato è veloce (er)?
Quindi, perché il codice Lisp compilato è veloce? Due situazioni:
codice Lisp non ottimizzato: il sistema runtime Lisp è ottimizzato per strutture dati dinamiche e codice non deve essere interpretato
codice Lisp ottimizzato: il compilatore Lisp bisogno di informazioni o inferisce e fa molto lavoro per emettere codice macchina ottimizzato.
Come programmatore Lisp, si consiglia di lavorare con il codice Lisp non ottimizzato, ma compilato, la maggior parte del tempo. È abbastanza veloce e offre molto comfort.
Diverse modalità di esecuzione di offrire scelta
Come programmatore Lisp abbiamo la scelta:
- interpretato codice: lento, ma più semplice per eseguire il debug
- codice compilato: veloce in fase di esecuzione, compilazione veloce, molti controlli del compilatore, leggermente più difficile da eseguire il debug, completamente dinamico
- codice ottimizzato: molto veloce in fase di esecuzione, possibilmente pericoloso in fase di esecuzione, un sacco di rumore compilazione di varie ottimizzazioni, lento compilazione
Tipicamente si ottimizzare solo le porzioni di codice che richiedono la velocità.
Ricorda che ci sono molte situazioni in cui anche un buon compilatore Lisp non può fare miracoli. Un programma orientato agli oggetti completamente generico (che utilizza il Common Lisp Object System) avrà quasi sempre un overhead (dispatching basato su classi di runtime, ...).
dinamicamente tipizzato e dinamico non sono la stessa cosa
Si noti inoltre che dinamicamente tipizzati e dinamica sono diverse proprietà di un linguaggio di programmazione:
Lisp è dinamicamente tipizzati poiché i controlli di tipo sono eseguiti in fase di esecuzione e le variabili di default possono essere impostate su tutti i tipi di oggetti. Per questo Lisp ha anche bisogno di tipi allegati agli oggetti dati stessi.
Lisp è dinamica perché sia il Lisp linguaggio di programmazione e il programma stesso possono essere modificate in fase di esecuzione: siamo in grado di aggiungere, modificare e rimuovere le funzioni, possiamo aggiungere, modificare o rimuovere costrutti sintattici, possiamo aggiungere, modificare o rimuovere i tipi di dati (record, classi, ...), possiamo cambiare la sintassi di superficie di Lisp in vari modi, ecc. Aiuta anche Lisp a scrivere dinamicamente per fornire alcune di queste funzionalità.
Interfaccia utente: la compilazione e smontaggio
ANSI Common Lisp fornisce
- due funzioni standard per compilare il codice: compile e compile file
- una funzione standard per caricare sorgente o compilato codice: load
- una funzione standard per smontare il codice: disassemble
E come può essere oggettivo-C dinamico e compilato? Beh ... il dinamismo contro la natura statica e la "compilazione" non descrivono la stessa proprietà. Un linguaggio può essere tipizzato staticamente e compilato (come C), tipizzato staticamente e interpretato (come C++ interpretato da Cling), digitato e compilato dinamicamente (come Objective-C, Lisp o JavaScript JIT-ed) e digitato e interpretato dinamicamente (come Python, PHP, Lua, ...). Non hanno davvero niente a che fare l'uno con l'altro. Il fatto che la tipizzazione statica renda più facile per un compilatore catturare errori e generare codice più efficiente è irrilevante. –
Per quanto riguarda "come aggiungerli": polimorfismo. Il compilatore genera codice che crea una sorta di trucco dinamico basato sui tipi (di runtime) di 'a' e' b'. –
Quindi perché Python compilato necessita di annotazioni di tipo? E Obj-C non ha annotazioni di tipo? – Aristides