Macros 避免运行时参数测试
我的原型程序最初定义了一些全局参数,这些参数随后会影响分析过程。但程序主体最终包含了大量的这些参数测试,以确定如何进行详细分析。例如:Macros 避免运行时参数测试,macros,global-variables,runtime,common-lisp,Macros,Global Variables,Runtime,Common Lisp,我的原型程序最初定义了一些全局参数,这些参数随后会影响分析过程。但程序主体最终包含了大量的这些参数测试,以确定如何进行详细分析。例如: (defparameter *param1* 0) (ecase *param1* (0 (foo)) (1 (bar)) (2 (baz))) 但是在运行时执行所有这些不同的测试是低效的。既然在分析开始之前所有参数都是已知的,那么这些测试能否有效地转移到编译时(或以其他方式处理) 我的第一个想法是为测试构建宏,因此在运行时只有相关代码可用: (
(defparameter *param1* 0)
(ecase *param1*
(0 (foo))
(1 (bar))
(2 (baz)))
但是在运行时执行所有这些不同的测试是低效的。既然在分析开始之前所有参数都是已知的,那么这些测试能否有效地转移到编译时(或以其他方式处理)
我的第一个想法是为测试构建宏,因此在运行时只有相关代码可用:
(defmacro param1-macro ()
(ecase *param1*
(0 `(foo))
(1 `(bar))
(2 `(baz))))
但是,像(param1 macro)这样的调用分散在各处会使代码在调试期间难以读取和分析。然后,每个宏调用的唯一决策过程都是非本地的。是否有更透明的方法来提高运行时效率?如果这些参数是编译时常量,则将它们设置为常量(即使用
defcontent
定义它们)。这将使编译器有机会在编译时对其值进行假设,并将条件转换为无条件执行
当然,编译器能做得多好取决于编译器:在我有限的测试中,至少有一些CL编译器能做这种优化
我当然会在用宏对编译器进行大量的二次猜测之前尝试这样做
当然,另一件事是提升测试(这必须有一个名称,但我不确定它是什么:我总是称它为“提升”)。把代码变成
(dotimes (i big-number)
(case *parameter*
((1) ...)
...))
进入
它将测试数量减少了一个大的因数big number
。我怀疑这也是一个好的编译器可以自己做的优化
最后,也许是最重要的一点:测量它有多慢。考试真的要花很长时间吗?几乎可以肯定,除非你衡量了他们的成本,否则你不会知道:当然,每当我做出这样的假设时,我都会发现这一点
作为上述方法的替代方法,这里有一个非常可怕的黑客攻击,它允许您使用参数,但会将其编译时值连接起来,从而导致编译器将其视为常量。注:当我说“可怕”时,我的意思也是“大部分未经测试” 以下是如何做到这一点:
(in-package :cl-user)
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *wiring* t))
(declaim (inline wiring))
(defun wiring (x)
x)
(define-compiler-macro wiring (&whole form x)
(if (and *wiring*
(symbolp x)
(boundp x))
`(quote ,(symbol-value x))
form))
现在,如果编译时*wiring*
为false,那么(wiring…
只是一个标识函数,所以您可以说(wiring*param*)
表示*param*
。它是内联声明的,所以它应该具有零成本(实际上,零用途)
但是如果编译时*wiring*
为true,那么编译器宏会做一件可怕的事情:(wiring*param*)
将:
*param*
是否为符号李>
*param*
绑定到2
,则
(case (wiring *param*)
((2) (+ x 0))
(otherwise (+ x 1)))
在编译时相当于
(case 2
((2) (+ x 0))
(otherwise (+ x 1)))
然后,编译器可以将其(在SBCL中带有注释)转换为(+x0)
这很可怕,因为。。。嗯,这有很多可怕的地方。它肯定违反了许多关于代码应该如何运行的假设。但它也有点可爱:你可以用Lisp语言在语言中实现这一点,我认为这很神奇。这在defconstant中如何工作?“常量”可能在后续运行中设置为不同的值。在运行之间重新编译所有内容是可以的,但这不会为defcontent生成编译器错误吗?(附言:使用SBCL)@davypough:是的,不允许重新定义常数,SBCL对此很挑剔。简单的方法是,如果重新定义Lisp,则只需在重新编译之前冷启动Lisp即可。如果你不能做到这一点,那么把文件包中定义的东西吹走就行了。@davypough:我添加了一个附录,其中有一个相当恶心的黑客,你可能想看看。警告买主!优雅的更新!这肯定会出现在我的工具箱中,尽管对于我当前的参数来说可能有点多。在一些评论中,在运行时检查大量参数的额外时间看起来很小(但不确定如何验证这一点,因为它们非常普遍)。把它们都放在一个单独的包里也是个好主意。谢谢你深刻的回答。
(case 2
((2) (+ x 0))
(otherwise (+ x 1)))