Rilegatura lessicale e rilegatura dinamica in generale
Considera il seguente esempio:
(let ((lexical-binding nil))
(disassemble
(byte-compile (lambda ()
(let ((foo 10))
(message foo))))))
Compila e disassembla immediatamente un semplice lambda
con una variabile locale. Con lexical-binding
disabilitato, come sopra, il codice byte ha il seguente aspetto:
0 constant 10
1 varbind foo
2 constant message
3 varref foo
4 call 1
5 unbind 1
6 return
Nota le istruzioni varbind
e varref
. Queste istruzioni associano e cercano rispettivamente le variabili in base al loro nome in un ambiente di rilegatura globale nella memoria dell'heap . Tutto ciò ha un effetto negativo sulle prestazioni: comporta hashing e confronto delle stringhe , sincronizzazione per l'accesso ai dati globali e accesso ripetuto alla memoria dell'heap che gioca male con la cache della CPU. Inoltre, le associazioni di variabili dinamiche devono essere ripristinate alla loro variabile precedente alla fine di let
, il che aggiunge n
ulteriori ricerche per ogni let
blocco con n
associazioni.
Se si legano lexical-binding
a t
nell'esempio di cui sopra, il bytecode sembra un po 'diversa:
0 constant 10
1 constant message
2 stack-ref 1
3 call 1
4 return
Si noti che varbind
e varref
sono completamente spariti. La variabile locale viene semplicemente inserita nello stack e definita da un offset costante tramite l' stack-ref
istruzione. In sostanza, la variabile viene rilegata e letta con tempo costante , letture e scritture di memoria nello stack , che è interamente locale e quindi gioca bene con la concorrenza e la cache della CPU e non comporta alcuna stringa.
In generale, con le ricerche di associazione lessicale delle variabili locali (ad es let
. setq
, Ecc.) Hanno una durata di funzionamento e una complessità della memoria molto inferiori .
Questo esempio specifico
Con l'associazione dinamica, ciascuno lascia incorrere in una penalità di prestazione, per motivi di cui sopra. Più permessi, più associazioni di variabili dinamiche.
In particolare, con un ulteriore let
all'interno del loop
corpo, la variabile associata dovrebbe essere ripristinata ad ogni iterazione del ciclo , aggiungendo una ricerca di variabili aggiuntiva ad ogni iterazione . Quindi, è più veloce tenere fuori il corpo del ciclo, in modo che la variabile di iterazione venga ripristinata una sola volta , dopo che l'intero ciclo è terminato. Tuttavia, questo non è particolarmente elegante, poiché la variabile di iterazione è legata molto prima che sia effettivamente richiesta.
Con il legame lessicale, le let
s sono economiche. In particolare, un let
body all'interno di un loop non è peggio (dal punto di vista delle prestazioni) di un let
esterno di un body loop. Pertanto, è perfettamente corretto associare le variabili il più localmente possibile e mantenere la variabile di iterazione confinata al corpo del loop.
È anche leggermente più veloce, perché compila molto meno istruzioni. Considera il successivo smontaggio affiancato (let locale sul lato destro):
0 varref list 0 varref list
1 constant nil 1:1 dup
2 varbind it 2 goto-if-nil-else-pop 2
3 dup 5 dup
4 varbind temp 6 car
5 goto-if-nil-else-pop 2 7 stack-ref 1
8:1 varref temp 8 cdr
9 car 9 discardN-preserve-tos 2
10 varset it 11 goto 1
11 varref temp 14:2 return
12 cdr
13 dup
14 varset temp
15 goto-if-not-nil 1
18 constant nil
19:2 unbind 2
20 return
Non ho idea, tuttavia, che cosa sta causando la differenza.
varbind
codice compilato in associazione lessicale. Questo è il punto e lo scopo.