如何让 SBCL 优化掉对 FDEFINITION 的可能调用?

How to make SBCL optimize away possible call to FDEFINITION?

抱歉:我没有足够的知识将其改写为易于理解的代码片段。

我一直在使用 SBCL 编译器注释作为可能改进的标志,但我对此深有体会 —

; compiling (DEFUN EXECUTE-PARALLEL ...)
; file: /home/dunham/8000-benchmarksgame/bench/spectralnorm/spectralnorm.sbcl-8.sbcl
; in: DEFUN EXECUTE-PARALLEL
;     (FUNCALL FUNCTION START END)
; --> SB-C::%FUNCALL THE 
; ==>
;   (SB-KERNEL:%COERCE-CALLABLE-FOR-CALL FUNCTION)
; 
; note: unable to
;   optimize away possible call to FDEFINITION at runtime
; because:
;   FUNCTION is not known to be a function

#+sb-thread
(defun execute-parallel (start end function)
  (declare (type int31 start end))
  (let* ((num-threads 4))
    (loop with step = (truncate (- end start) num-threads)
          for index from start below end by step
          collecting (let ((start index)
                           (end (min end (+ index step))))
                       (sb-thread:make-thread
                        (lambda () (funcall function start end))))
          into threads
          finally (mapcar #'sb-thread:join-thread threads))))

#-sb-thread
(defun execute-parallel (start end function )
  (funcall function start end))

(程序为here. Measurements for similar programs are here。)

制作 SBCL "optimize away possible call to FDEFINITION" 是否可行,或者编译器是否注意解释而不是机会?

可能调用fdefinition的原因是它不知道function是一个函数:它可能是一个的名字:一般来说它可能是一个函数指示符 而不是函数。为了让编译器保持安静,向它解释它是一个具有合适类型声明的函数,即 (declare (type function function)):你只需要声明它的类型是 function).

Rainer 是对的:假设您正在启动一个新线程,这将成为性能问题的可能性为 ε。特别是添加声明很可能根本没有任何区别:

  • 如果没有声明,对 funcall 的调用将被编译为 'check the type of the object: if it is a function, call it; if it is not, call fdefinition on it and call the result;';
  • 加上声明,整个函数看起来像 'check the object is a function, signalling an error if not ... call the function'。

在这两种情况下,如果对象是一个函数,则有一个类型检查和一个调用:类型检查只是在不同的地方。在第一种情况下,如果对象只是一个函数的名称,代码仍然可以工作,而类型检查则不会。

在这两种情况下,这是您关心调用 make-thread 的代码:如果这是 任何类似 的函数调用,即使通过 fdefinition 线程系统给我留下了深刻的印象!几乎可以肯定,此函数的性能完全取决于创建线程的开销。

在实际代码中,避免这样的优化——除非真的需要

Is it practical to make SBCL "optimize away possible call to FDEFINITION" or is that compiler note an explanation rather than an opportunity?

一般来说这无关紧要,尤其是因为大多数 Lisp 代码 不应该 以优化质量 (speed 3) (safety 0) (space 0) 编译,因为它可能会使软件出现运行时错误并且崩溃取决于所使用的实现和程序。通过 funcall 调用未经检查的东西(没有 safety),而不是函数或符号命名函数,可能很危险,足以导致程序崩溃。

对于特定的基准测试,可以通过计时检查类型声明和专门的 fdefinition 编译是否带来任何优势。

类型声明

一个类型声明来明确一个名为 fn 的变量正在引用一个类型 function 的对象将是:

(declare (type function fn))

在具体的基准测试程序中无论如何都不会调用FDEFINITION

在您提供的示例中,无论如何都不会调用 fdefinition

(setf foo (lambda (x) x))       ; foo references a function object

(funcall foo 3)

funcall大概是这样实现的:

(etypecase f
  ((or cons symbol) (funcall (fdefinition f) ...))
  (function         ...))

由于您的代码传递了一个函数对象,因此永远不需要调用 fdefinition

然后优化的好处是可以删除运行时类型分派和 cons 或符号情况的死代码...

你问了一个关于删除 fdefinition 的问题,但实际上你的问题依赖于一个前提,即 sbcl 注释是推动优化和改进的好方法。这些注释是发现明显问题和类型声明可以提供帮助的地方的好方法。他们不会告诉您实际上是什么让您的程序变慢。提高程序性能的正确方法是 1. 思考是否有更快的算法,以及 2. 衡量它的性能并找出慢的地方。

单个 fdefinition 调用只有在紧密循环中发生时才有意义(即它不是单个而是非常复数)

在这种情况下它恰好启动了一个线程。如果您在紧密循环中启动线程,那么您的性能问题来自在紧密循环中启动线程。不要那样做。

如果您没有在紧密循环中启动线程(查看您的代码,似乎您没有),还有更大的鱼要炸。当您可以优化内部函数时,为什么要在每次调用 execute-parallel 时可能被调用 4 次的 fdefinition 上浪费时间。