Common Lisp:优化SBCL中的功能

时间:2017-12-04 13:34:39

标签: common-lisp

对于2D图形,我需要优化我的功能,但在SBCL中我得到很多关于SBCL无法内联算术运算的注释。我尝试了各种各样的声明,但它似乎并没有使编译器感到满意。这是一个简单的例子:

(defun test-floor (x div)
  (declare (type single-float x)
           (type (signed-byte 64) div)
           (optimize (speed 3)))
  (floor x div))

给出以下4个注释。我完全失去了#' floor是一个内置函数。我试图找到关于如何在SBCL中正确地给出编译器提示的信息/教程,并且没有找到正确的信息,所以任何信息都将非常感谢!不幸的是,Common Lisp中的优化对我来说是个未知领域。我在Linux机器上使用SBCL 1.3.20。

; file: /tmp/file595dqU
; in: defun test-floor
;     (FLOOR CL-FLOCKS::X CL-FLOCKS::DIV)
; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL TRUNCATE LET* 
; ==>
;   (SB-KERNEL:%UNARY-TRUNCATE/SINGLE-FLOAT (/ SB-C::X SB-C::F))
; 
; note: forced to do full call
;       unable to do inline float truncate (cost 5) because:
;       The result is a (values integer &optional), not a (values
;                                                          (signed-byte 64) &rest
;                                                          t).

; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL TRUNCATE LET* VALUES - * 
; ==>
;   (SB-KERNEL:%SINGLE-FLOAT SB-C::RES)
; 
; note: forced to do full call
;       unable to do inline float coercion (cost 5) because:
;       The first argument is a integer, not a (signed-byte 64).

; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL FUNCTION IF VALUES 1- 
; ==>
;   (- SB-C::TRU 1)
; 
; note: forced to do generic-- (cost 10)
;       unable to do inline fixnum arithmetic (cost 1) because:
;       The first argument is a integer, not a fixnum.
;       The result is a (values integer &optional), not a (values fixnum &rest t).
;       unable to do inline fixnum arithmetic (cost 2) because:
;       The first argument is a integer, not a fixnum.
;       The result is a (values integer &optional), not a (values fixnum &rest t).
;       etc.

; --> MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL FUNCTION IF VALUES 
; ==>
;   (+ REM SB-C::DIVISOR)
; 
; note: doing signed word to integer coercion (cost 20) from div, for:
;       the second argument of generic-+
; 
; compilation unit finished
;   printed 4 notes

CL-USER> 

3 个答案:

答案 0 :(得分:2)

当你调用floor时,你必须处理不同的数字子类型:代码将float除以整数(可能涉及将整数强制转换为float),然后必须强制返回结果到整数。这是必须正确完成的工作量,如果您不限制输入类型,则不太可能绕过它。

如果您使用ffloor,那么主要结果是浮点数(当您真正需要它时,您仍然可以将其四舍五入为整数(例如转换为像素坐标))。以下代码不提供编译说明:

(defun test-floor (x div)
  (declare (type single-float x)
           (type fixnum div)
           (optimize (speed 3)))
  (ffloor x div))

您甚至可以将div声明为float,这将推动向调用者提供适当类型值(以及执行运行时检查)的责任。

另请注意,在定义函数之前,您应该(declaim (inline test-floor));这有帮助,因为编译器可以在代码中放置快捷方式,以避免检查输入参数类型和装箱结果。

编辑:

浮子的范围覆盖了一个很大的可能域(由于指数):在零附近更密集地堆积,更多地间隔到无穷大。整数值是线性间隔的,但覆盖较小的范围,具有相同的位数。因此,如果您希望保证输出适合fixnum,则必须确保输入中的浮点数不会超出fixnum的范围。我尝试了以下方法:

(defun test-round (x)
  (declare (type (single-float #.(float most-negative-fixnum 0f0)
                               #.(float (/ most-positive-fixnum 2) 0f0)) x)
           (optimize (speed 3)))
  (round x))

我必须将浮子的上限范围的一半,因为当你测试时:

(typep (round (coerce most-positive-fixnum 'single-float)) 'fixnum)

...它返回NIL。我没有太多时间来了解为什么会发生这种情况,但这取决于您的实施和架构。取最正的fixnum的一半确保该值足够低,可以转换为fixnum。现在,我没有更多的汇编说明。

(同样适用于(signed-byte 64)))

NB。与上面的示例不同,您应该使用deftype并避免在任何地方重复相同的声明。

答案 1 :(得分:2)

如果要指定表达式的返回值,可以使用THE

(the fixnum (1+ 3))

但是你真的想确保该值实际上是一个fixnum。如果你是谎言,那么Lisp可能会相信你并且你有未指定的运行时效果。 SBCL可能会在编译时发出警告,但你真的应该照顾它。如果提供的错误类型是返回错误的类型,数据可能会损坏和/或Lisp可能会崩溃。

指定返回值的另一种方法是FTYPE声明:

例如,函数ith可以将整数和列表作为参数。它返回任意类型 - > T或任何子类型。

(declaim (ftype (function (integer list) t)
                ith))

例如:

(the fixnum (+ (the fixnum a) (the fixnum b)))

你需要确定:

  • a是fixnum
  • b是fixnum
  • a和b的总和也总是一个fixnum

这里更容易,因为a和b的总和肯定是fixnum:

CL-USER 3 > (let ((a 3) (b 12))
              (the fixnum (+ (the (integer 0 10) a)
                             (the (integer 3 20) b))))
15

Lisp可能会在运行时和/或编译时检查它。现在添加可以是一个简单的fixnum操作,不需要处理fixnum溢出和bignums。如果将safety值设置为低值,则也可以省略运行时检查。但是:你永远不应该用错误的类型调用这段代码。

答案 2 :(得分:1)

SBCL声称它无法优化对floor的调用,因为它不确定返回值是否足够小以适应64位整数。

CL-USER> (test-floor 1f25 1234)
8103727629894569426944 ;
0.0
CL-USER> (format nil “~b” *)
;; a long binary string 
CL-USER> (length *)
73

可能会返回73位整数但不适合64位。

也是SBCL手册:

编辑:经过一些搜索,我找到了floor的转换。它是here。我在下面重现:

(deftransform floor ((number divisor))
  `(multiple-value-bind (tru rem) (truncate number divisor)
     (if (and (not (zerop rem))
              (if (minusp divisor)
                  (plusp number)
                  (minusp number)))
         (values (1- tru) (+ rem divisor))
         (values tru rem))))

这样就解释了编译器消息的内容