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)))