如何使用类型,以便在 sbcl 中内联(或"open coded")泛型操作?



SBCL编译器的优化基于这样一种思想,即如果声明了一个类型,则"开放编码";允许用特定操作替换通用操作。例如

(defun add (a b)
(declare (type fixnum a b))
(+ a b))

将允许用fixnum的单个指令替换通用+

然而,我发现在实践中,这似乎很少可能,因为:

  1. 为了使函数专门化/优化,它必须是可内联的。声明必须用(declaim (inline ...))显式标记,因此函数的作者必须预料到其他人可能想要内联它。(理论上,编译器可以生成多个版本,但事实并非如此。(
  2. 大多数标准函数看起来都不可内联

例如,可以预期以下声明足以进行开放编码:

(defun max-integers (array)
(declare (optimize (speed 3) (space 0) (safety 0)))
(declare (inline reduce))
(declare (type (simple-array fixnum (*)) array))
(reduce (lambda (a b) (if (> b a) b a)) array))

然而,程序集显示它正在对泛型reduce:进行函数调用

; Size: 22 bytes. Origin: #x1001BC8109
; 09:       488B15B0FFFFFF   MOV RDX, [RIP-80]                ; no-arg-parsing entry point
; #<FUNCTION (LAMBDA
;                # ..)>
; 10:       B904000000       MOV ECX, 4
; 15:       FF7508           PUSH QWORD PTR [RBP+8]
; 18:       B8781C3220       MOV EAX, #x20321C78              ; #<FDEFN REDUCE>
; 1D:       FFE0             JMP RAX

结论似乎是编译器实际上无法进行太多的类型优化,因为reducemap等的每次使用都是类型传播的障碍,它们是其他一切的构建块。如何通过声明类型来克服这一问题并利用优化?我真的想避免编写每个函数的特定类型版本或";宏观化";什么应该是函数。

我认为一个答案是,如果你想写FORTRAN风格的数组抨击代码,就写FORTRAN-风格的数组攻击代码。特别是使用像reduce这样的东西可能不是实现这一点的方法。

例如,如果您将功能更改为完全可读的

(defun max-integers/loop (array)
(declare (optimize (speed 3) (space 0) (safety 0))
(type (simple-array fixnum (*)) array))
(loop for i of-type fixnum across array
maximizing i))

然后SBCL在优化它方面做得更好


值得指出你的问题中的另一个困惑:你对这样的东西这么说

(defun add (a b)
(declare (type fixnum a b))
(+ a b))

SBCL将根据机器指令优化+。不,不会的。它不会这样做的原因是fixnum类型在加法下没有闭合:考虑(add most-positive-fixnum 1)应该做什么。如果你想为整数生成非常快速的代码,你需要确保你的整数类型足够小,编译器可以确保你对它们进行的操作仍然是机器整数(或者,如果你想危险地生活,可以用(the fixnum ...)覆盖你的代码,并在编译时将safety设置为0,这似乎允许编译器像人们通常期望的计算机那样返回错误的加法答案(。

您不能强制实现打开定义时未声明为INLINE的代码函数——它只是没有保存所需的信息。

然而,与实际处理相比,调用REDUCE的开销可能可以忽略不计。因此,可以声明ab的类型,以优化回调函数。

(reduce (lambda (a b) (declare (type fixnum a b)) (if (> b a) b a)) array)

我想您希望,如果它打开编码的reduce,它会从array的声明中自动传播这种类型,所以您不需要这样做。

最新更新