【问题标题】:Common Lisp: Optimizing functions in SBCLCommon Lisp:优化 SBCL 中的函数
【发布时间】:2017-12-04 13:34:39
【问题描述】:

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

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

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

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

    如果您改为使用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));这很有帮助,因为编译器可以在代码中放置快捷方式以避免检查输入参数类型和装箱结果。

    编辑:

    float 的范围覆盖了一个很大的可能域(由于指数):在零附近更密集,向无穷大间隔更远。整数值是线性间隔的,但在相同位数的情况下覆盖的范围更小。因此,如果您想保证您的输出适合 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) 也是如此))

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

    【讨论】:

    • 谢谢,这真的很有帮助!正如您正确假设的那样,我做了很多像素计算,并且像素存储在数组中。但是我还没有找到一种没有编译器投诉的方法来舍入浮点数:(defun test-round (x) (declare (type single-float x) (optimize (speed 3))) (round x))
    • 编译器抱怨结果是整数而不是(有符号字节 64)。如果我能以某种方式告诉编译器该值实际上 is 在 64 位范围内,那就太好了。
    • 问题是:你怎么知道结果在 64 位范围内?如果不是这种情况应该怎么办? SBCL 非常努力地防止您做不正确的事情。您应该限制输入浮点域,以便结果必须在预期范围内(将编辑)
    • 我正在考虑按照这些思路来限制输入浮点域,因为我正在做的只是屏幕分辨率内的矢量数学运算,所以我可以安全地假设输入值是理智的。我什至考虑使用 fixnum 数学和查找表来进行非有理运算,但我什至不确定考虑到当前 CPU 中的 FP 单元,这是否真的更快。
    【解决方案2】:

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

    (the fixnum (1+ 3))
    

    但您确实想确保该值实际上是一个固定编号。如果你“撒谎”,那么 Lisp 可能会相信你并且你有未指定的运行时效果。 SBCL 可能会在编译时发出警告,但您确实应该注意这一点。如果您提供错误的类型返回错误的类型,数据可能会损坏和/或 Lisp 可能会崩溃。

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

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

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

    例如:

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

    您需要确保:

    • a 是一个固定编号
    • b 是一个固定编号
    • a 和 b 的总和也总是一个固定数

    这里比较简单,因为 a 和 b 的和肯定是一个固定数:

    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 值设置为较低的值,则运行时检查也可能会被忽略。但是:你不应该用错误的类型调用这段代码。

    【讨论】:

    • 谢谢,这也很有用!虽然原则上一切都很清楚,但我只是不知道如何在代码中指定它,而且似乎很难找到关于这个主题的信息。所有这些 cmets 肯定有助于更好地理解 CL/SBCL 中的优化。感谢大家抽出宝贵时间提供帮助!
    【解决方案3】:

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

    这解释了编译器消息在谈论什么

    【讨论】:

    • 这似乎很合理,谢谢!有没有办法告诉编译器结果永远不会超出 64 位范围?还是按照 coredump 的回答中的建议使用 ffloor 会更有效吗?
    • 我还没有找到任何东西
    猜你喜欢
    • 2016-10-27
    • 1970-01-01
    • 2017-02-27
    • 2015-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多