问题描述
SBCL编译器的优化基于这样的思想:如果声明了类型,则“开放式编码”允许将通用操作替换为特定操作。 例如
(defun add (a b)
(declare (type fixnum a b))
(+ a b))
将允许将通用+
替换为Fixnum的一条指令。
但是,我发现实际上这几乎是不可能的,因为:
- 要使功能专门化/优化,它必须是可插入的。该声明必须用
(declaim (inline ...))
明确标记,因此函数的作者必须预料到其他人可能希望对其进行内联。 (理论上,编译器可以生成多个版本,但是事实并非如此。) - 大多数标准函数似乎不是内联的。
例如,人们希望以下声明足以进行开放式编码:
(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))
; 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
结论似乎是编译器实际上不能做很多类型优化,因为reduce
,map
等的每种用法都是类型传播的障碍,它们构成了其他所有内容的构建块。
我如何克服这个问题并通过声明类型来利用优化?
我真的想避免为每个函数编写特定于类型的版本,或者“夸大”应该是什么函数。
解决方法
我认为一个答案是,如果您想编写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
的开销可以忽略不计。因此, 可以做的是声明a
和b
的类型,以优化回调函数。
(reduce (lambda (a b) (declare (type fixnum a b)) (if (> b a) b a)) array)
我猜您希望如果它对reduce
进行开放编码,它将自动从array
的声明中传播这种类型,因此您不需要这样做。