Mengikat leksikal versus pengikatan dinamis secara umum
Perhatikan contoh berikut:
(let ((lexical-binding nil))
(disassemble
(byte-compile (lambda ()
(let ((foo 10))
(message foo))))))
Ini mengkompilasi dan segera membongkar sederhana lambda
dengan variabel lokal. Dengan lexical-binding
dinonaktifkan, seperti di atas, kode byte terlihat sebagai berikut:
0 constant 10
1 varbind foo
2 constant message
3 varref foo
4 call 1
5 unbind 1
6 return
Perhatikan varbind
dan varref
instruksinya. Instruksi ini mengikat dan mencari masing-masing variabel dengan namanya di lingkungan pengikatan global pada memori tumpukan . Semua ini memiliki efek buruk pada kinerja: Ini melibatkan string hashing dan perbandingan , sinkronisasi untuk akses data global, dan akses memori tumpukan berulang yang bermain buruk dengan caching CPU. Juga, binding variabel dinamis perlu dikembalikan ke variabel sebelumnya di akhir let
, yang menambahkan n
pencarian tambahan untuk setiap let
blok dengan n
binding.
Jika Anda mengikat lexical-binding
untuk t
di contoh di atas, kode byte terlihat agak berbeda:
0 constant 10
1 constant message
2 stack-ref 1
3 call 1
4 return
Catat itu varbind
dan varref
sepenuhnya hilang. Variabel lokal hanya didorong ke stack, dan disebut dengan offset konstan melalui stack-ref
instruksi. Pada dasarnya, variabel terikat dan dibaca dengan waktu konstan , memori in-stack membaca dan menulis, yang sepenuhnya lokal dan dengan demikian bermain baik dengan konkurensi dan caching CPU , dan tidak melibatkan string sama sekali.
Umumnya, dengan pencarian mengikat leksikal variabel lokal (misalnya let
, setq
, dll) memiliki lebih sedikit runtime dan memori kompleksitas .
Contoh spesifik ini
Dengan pengikatan dinamis, masing-masing membiarkan dikenakan penalti kinerja, untuk alasan di atas. Semakin banyak memungkinkan, binding variabel lebih dinamis.
Khususnya, dengan tambahan let
di dalam loop
tubuh, variabel terikat perlu dikembalikan pada setiap iterasi dari loop , menambahkan pencarian variabel tambahan untuk setiap iterasi . Oleh karena itu, lebih cepat untuk menjaga keluar dari tubuh loop, sehingga variabel iterasi hanya direset sekali , setelah seluruh loop selesai. Namun, ini tidak terlalu elegan, karena variabel iterasi terikat sebelum benar-benar diperlukan.
Dengan pengikatan leksikal, let
s itu murah. Khususnya, di let
dalam tubuh loop tidak lebih buruk (kinerja-bijaksana) daripada di let
luar tubuh loop. Oleh karena itu, sangat baik untuk mengikat variabel secara lokal mungkin, dan menjaga variabel iterasi terbatas pada badan loop.
Ini juga sedikit lebih cepat, karena mengkompilasi instruksi jauh lebih sedikit. Pertimbangkan pembongkaran berdampingan yang diikuti (biarkan lokal di sisi kanan):
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
Saya tidak tahu, apa yang menyebabkan perbedaan.
varbind
kode yang dikompilasi di bawah pengikatan leksikal. Itulah inti dan tujuannya.;; -*- lexical-binding: t -*-
, memuatnya, dan memanggil(byte-compile 'sum1)
, dengan asumsi bahwa menghasilkan definisi yang dikompilasi di bawah pengikatan leksikal. Namun, sepertinya tidak ada.byte-compile
dengan buffer yang sesuai saat ini, yang — omong-omong — persis apa yang dilakukan oleh byte compiler. Jika Anda memohonbyte-compile
secara terpisah, Anda perlu mengatur secara eksplisitlexical-binding
, seperti yang saya lakukan dalam jawaban saya.