如果这些参数是编译时常量,则将它们设为常量(即用defconstant 定义它们)。这应该让编译器有机会在编译时对其值做出假设,并将条件转换为无条件执行。
编译器能做到这一点当然取决于编译器:在我有限的测试中,至少一些 CL 编译器会进行这种优化。
我肯定会在大量使用宏进行二次猜测编译器之前尝试这样做。
当然,另一件事是引发测试(这必须有一个名字,但我不确定它是什么:我总是称它为“引发”)。转码喜欢
(dotimes (i big-number)
(case *parameter*
((1) ...)
...))
进入
(case *parameter*
((1) (dotimes (i big-number)
...))
...)
这将测试次数减少了big-number。我怀疑这也是好的编译器可以自己做的优化。
最后,也许是最重要的:衡量它有多慢。测试真的需要很长时间吗?几乎可以肯定,除非你测量过他们的成本,否则你不会知道:当然,每当我做出这样的假设时,我都会发现!
作为上述的替代方案,这里有一个非常可怕的 hack,它允许您使用参数,但会连接它们的 compile-time 值,从而导致编译器将它们视为常量。注意:当我说“可怕”时,我的意思也是“几乎没有经过测试”。
您可以这样做:
(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* 在编译时为假,(wiring ...) 只是一个恒等函数,所以你可以说(wiring *param*) 表示*param*。它被声明为内联,因此它应该具有零成本(并且实际上是零用途)。
但是如果*wiring* 在编译时是true,那么编译器宏会做一件可怕的事情:(wiring *param*) 会:
- 检查
*param*是否为符号;
- 如果是,并且如果它被绑定,那么它将扩展到它的 compile-time 值(为安全起见)。
这意味着,如果在编译时,*param* 绑定到 2,那么
(case (wiring *param*)
((2) (+ x 0))
(otherwise (+ x 1)))
在编译时等价于
(case 2
((2) (+ x 0))
(otherwise (+ x 1)))
然后编译器可以将它(在 SBCL 的情况下带有注释)转换为 (+ x 0)。
这太可怕了,因为……好吧,可怕的地方有很多。它肯定违反了很多关于代码应该如何表现的假设。但它也有点可爱:你可以在 Lisp 语言中做到这一点,我认为这很棒。