首页 > 解决方案 > 如何说服 Lisp SBCL 进行内联 fixnum 算术?

问题描述

我在其他 SO 答案中找到了一些技术,但显然我无法说服 SBCL 进行内联 fixnum 算术:

(declaim (optimize (speed 2) (safety 1)))

(declaim (ftype (function (fixnum fixnum) double-float) fixnumtest) (inline fixnumtest))
(defun fixnumtest (i j)
  (declare (type fixnum i j))
  (let* ((n (the fixnum (+ i j)))
         (n+1 (the fixnum (1+ n))))
    (declare (type fixnum n n+1))
    (/ 1.0d0 (the fixnum (* n n+1)) )
  )
)

(defun main () 
  (format t "~11,9F~%" (fixnumtest 2 3))
) 

:结果是forced to do GENERIC-* (cost 30)

我还应该尝试什么?

$ sbcl --eval '(load (compile-file "play.lisp"))'
This is SBCL 1.5.1,
…
; compiling file "/opt/tmp/play.lisp" (written 16 OCT 2019 08:03:15 PM):
; compiling (DECLAIM (OPTIMIZE # ...))
; compiling (DECLAIM (FTYPE # ...) ...)
; compiling (DEFUN FIXNUMTEST ...)
; file: /opt/tmp/play.lisp
; in: DEFUN FIXNUMTEST
;     (* N N+1)
; 
; note: forced to do GENERIC-* (cost 30)
;       unable to do inline fixnum arithmetic (cost 4) because:
;       The result is a (VALUES
;                        (INTEGER -21267647932558653961849226946058125312
;                         21267647932558653961849226946058125312)
;                        &OPTIONAL), not a (VALUES FIXNUM &REST T).
;       unable to do inline (signed-byte 64) arithmetic (cost 5) because:
;       The result is a (VALUES
;                        (INTEGER -21267647932558653961849226946058125312
;                         21267647932558653961849226946058125312)
;                        &OPTIONAL), not a (VALUES (SIGNED-BYTE 64) &REST T).
;       etc.

另外,我认为这doing float to pointer coercion (cost 13)是从函数返回浮点数的普通结果是否正确?

;     (DEFUN FIXNUMTEST (I J)
;       (DECLARE (TYPE FIXNUM I J))
;       (LET* ((N (THE FIXNUM #)) (N+1 (THE FIXNUM #)))
;         (DECLARE (TYPE FIXNUM N N+1))
;         (/ 1.0d0 (THE FIXNUM (* N N+1)))))
; --> PROGN SB-IMPL::%DEFUN SB-IMPL::%DEFUN SB-INT:NAMED-LAMBDA 
; ==>
;   #'(SB-INT:NAMED-LAMBDA FIXNUMTEST
;         (I J)
;       (DECLARE (SB-C::TOP-LEVEL-FORM))
;       (DECLARE (TYPE FIXNUM I J))
;       (BLOCK FIXNUMTEST
;         (LET* ((N #) (N+1 #))
;           (DECLARE (TYPE FIXNUM N N+1))
;           (/ 1.0d0 (THE FIXNUM #)))))
; 
; note: doing float to pointer coercion (cost 13) to "<return value>"

标签: optimizationlispcommon-lispsbclfixnum

解决方案


好吧,编译器正在告诉你答案,也许是以一种有点无益的方式。如果您有两个 fixnum,那么例如添加它们会导致 fixnum 的情况并非如此:该类型在fixnum算术运算下不会关闭(甚至在+,-和下也不会关闭)。*/

SBCL 手册

SBCL 编译器对类型声明的处理与大多数其他 Lisp 编译器不同。在默认编译策略下,编译器不会盲目相信类型声明,而是认为它们是关于应该检查的程序的断言:所有未被证明始终成立的类型声明都在运行时断言。

如果你想编译机器算术,你需要做的是告诉编译器它使用的类型足够好,它可以知道结果类型足够好,可以立即表示。

给定函数中的算术,并假设是 64 位实现,那么一个好的类型是(signed-byte 31):它很想使用(signed-byte 32),但是失败了,因为你最终得到的东西比 a 大(signed-byte 64)

因此,除了在返回时使用最终的双浮点数之外,此代码不会发出警告:

(deftype smallish-integer (&optional (bits 31))
  `(signed-byte ,bits))


(declaim (ftype (function (smallish-integer smallish-integer) double-float)
                fixnumtest)
         (inline fixnumtest))

(defun fixnumtest (i j)
  (declare (optimize (speed 2)))
  (declare (type smallish-integer i j))
  (let* ((n (+ i j))
         (n+1 (1+ n)))
    (/ 1.0d0 (* n n+1))))

值得注意的(signed-byte 64)是 a 比 a 大得多fixnum:这没关系,因为在函数中编译器可以处理适合寄存器的数字,即使它们大于 fixnums。

我对 x64 汇编器不够熟悉,无法检查所有算术是否被编译为机器指令,但看起来确实如此。

有可能说服 SBCL 编译器你不关心得到正确的答案,它应该只做机器算术,即使它知道它可能会溢出。我不知道该怎么做。


推荐阅读