一般而言,词汇绑定与动态绑定
考虑以下示例:
(let ((lexical-binding nil))
(disassemble
(byte-compile (lambda ()
(let ((foo 10))
(message foo))))))
它会编译并立即反汇编lambda
带有局部变量的简单变量。随着lexical-binding
残疾人,如上,字节代码如下:
0 constant 10
1 varbind foo
2 constant message
3 varref foo
4 call 1
5 unbind 1
6 return
注意varbind
和varref
说明。这些指令分别在堆内存的全局绑定环境中按名称分别绑定和查找变量。所有这些都会对性能产生不利影响:它涉及字符串散列和比较,用于全局数据访问的同步以及对CPU缓存的不良影响的重复堆内存访问。另外,动态变量绑定需要在末尾恢复到其先前的变量,这会为每个具有绑定的块添加额外的查找。let
n
let
n
如果您绑定lexical-binding
到t
在上面的例子中,字节代码看起来有点不同:
0 constant 10
1 constant message
2 stack-ref 1
3 call 1
4 return
请注意,varbind
并且varref
完全消失了。只需将局部变量压入堆栈,并通过stack-ref
指令以恒定偏移量进行引用即可。本质上,变量绑定,并用读恒定时间,在堆栈存储器中读取和写入,这是完全本地并因此并发和CPU的高速缓存以及播放,并且不涉及到有任何字符串。
一般来说,局部变量的词法绑定查询(例如let
,setq
等)有少得多的运行时间和存储的复杂性。
这个具体的例子
使用动态绑定,由于上述原因,每个let都会导致性能下降。让得越多,动态变量绑定就越多。
值得注意的是,体内有一个额外let
的loop
变量,绑定的变量将需要在循环的每次迭代中恢复,从而为每次迭代添加一个额外的变量查找。因此,将let保留在循环主体之外的速度更快,这样,在整个循环完成之后,仅将迭代变量重置一次。但是,这并不是特别优雅,因为迭代变量在实际需要之前就已经绑定了。
使用词法绑定,let
s很便宜。值得注意的是,let
在循环体内(在性能方面)并不比在循环体外更差let
。因此,将变量尽可能地局部绑定,并将迭代变量限制在循环主体中是完全可以的。
它也稍快一些,因为它可以编译更少的指令。考虑下面的并排反汇编(右侧的本地让):
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
不过,我不知道是什么导致了差异。
varbind
在词法绑定下没有编译的代码。这就是重点和目的。