聊一聊 Emacs 的字节编译
某些 Lisp 有一项独门绝迹,那就是可以在任意时刻将函数编译成字节码/本地代码(具体编译成什么有赖于方言和具体实现)。
对于那些能够编译的 Lisp 函数(例如 CLISP ),一般都支持两种形式的代码:一种是缓慢的,未优化未编译过的代码,一种是快速,高效编译过的代码。
但是你不要以为未编译的代码就一无是处了,除了无需花费时间在编译上之外,它也有其他的有点。
对于 Emacs Lisp 来说,未编译过的函数就是一个 lambda 表达式。只不过它存在于一个 symbol 中,就相当与给它取了个名字。
而编译后的 form 则是一个(特殊的) 数组,编译后的字节码以字符串的形式存在这个数组的第二个元素中。其他的元素还包括常量,docstring 等。
Elisp 提供了 byte-compile
函数来编译其他函数。该函数能接受一个 lambda 函数或者一个 symbol,不过如果参数是 symbol 的话,编译后的函数回覆盖该 symbol 的原 S 表达式。
(byte-compile (lambda (x) (* 2 x)))
=> #[(x) "^H\301_\207" [x 2] 2]
编译器不仅仅是将函数是转换成字节码并展开宏,还会进行一些优化,比如删除无用代码,预先执行 safe constant forms, 以及内联函数,从而极大地提高执行效率。
(通过我的 measure-time macro 来测算的)。
(defun fib (n)
"Fibonacci sequence."
(if (<= n 2) 1
(+ (fib (- n 1)) (fib (- n 2)))))
(measure-time
(fib 30))
=> 1.0508708953857422
(byte-compile 'fib)
(measure-time
(fib 30))
=> 0.4302399158477783
大多数 Emacs 自带的函数都预先编译过了。不过也有部分自带的函数没有被编译,所以我想,不如我来花点时间把它们编译一下算了。
Common Lisp 是有提供一个判断函数(compiled-function-p) 来测试另一个函数是否有编译过的。
但不知为何,Elisp 中没有预定义这样一个函数,我只能自己写一个了:
(defun byte-compiled-p (func)
"Return t if function is byte compiled."
(cond
((symbolp func) (byte-compiled-p (symbol-function func)))
((functionp func) (not (sequencep func)))
(t nil)))
我一开始的想法是便利所有 interned symbol,若通过上面的测试函数发现该 symbol 的 function slot 中是未编译的函数,那么就调用 byte-compile
来编译它。
不过后来我发现, byte-compile
足够聪明,它会自动忽略那些没有函数以及那些函数以及编译过的 symbol。
那么,下一个问题就是,我们如何遍历每个 interned symbol? 函数 mapatoms
可以做到这一点. 给它一个函数作为参数,它就会将该函数应用到每个 interned symbol 上。
这样一来,问题就简单了. (译者注:我运行了这个语句之后,Evil 彻底用不了了,不得不重启 Emacs...)
(mapatoms 'byte-compile)
这就搞定了!整个过程不过几秒钟,不过会产生大量的警告. 目前我还未找到屏蔽这些警告方法,所以你最好不要自动运行这个语句,否则你会发现时不时地会弹出一个 window。
而且,我也不确定这么做会不会损坏你的 Emacs session,毕竟不是所有的函数写的时候都有好好的考虑编译后还能不能用,尤其是那些用到宏的函数。
另外,我很怀疑这样能否带来性能上的明显提升,就像我前面所得,大多数的函数预先编译过了,而这部分函数才是使用最频繁的函数。
把所有函数都编译一次,可能只是给你你心理上安慰一点而已。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 口袋中的 org-mode
下一篇: 令人惊喜的 Kotlin 特性
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论