Как убедить Lisp SBCL сделать арифметику inline fixnum? - PullRequest
1 голос
/ 17 октября 2019

Я нашел некоторые приемы в других ответах 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>"

Ответы [ 2 ]

3 голосов
/ 17 октября 2019

Ну, компилятор говорит вам ответ, возможно, немного бесполезно. Если у вас есть два фикснума, то это не тот случай, когда, например, их добавление приводит к фиксиннуму: тип fixnum не закрывается при арифметических операциях (даже при +, - и *,пренебрегая /).

Из руководства SBCL :

Компилятор SBCL трактует объявления типов иначе, чем большинство других компиляторов Lisp. В соответствии с политикой компиляции по умолчанию компилятор не верит слепо в объявления объявлений типов, а рассматривает их как утверждения о программе, которые должны быть проверены: все объявления типов, которые не всегда поддерживаются, создаются во время выполнения.

Что вам нужно сделать, если вы хотите скомпилировать машинную арифметику, так это сообщить компилятору, что используемые им типы достаточно хороши, чтобы он мог знать, что типы результатов достаточно хороши и могут быть представлены немедленно.

Учитывая арифметику, которую вы имеете в функции, и предполагая 64-битную реализацию, тогда хорошим типом будет (signed-byte 31): заманчиво использовать (signed-byte 32), но это не получается, потому что вы в конечном итоге получаете нечто большее, чем (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) намного больше, чем fixnum: это нормально, потому что внутри функции компилятор может справиться с числами, которые вписываются в регистрrs, хотя они больше, чем fixnums.

Я недостаточно знаком с ассемблером x64, чтобы проверить, что вся арифметика скомпилирована как машинные инструкции, но выглядит так.

Можетможно было бы убедить компилятор SBCL в том, что вам не нужен правильный ответ и что он должен просто выполнять машинную арифметику, даже если он знает, что он может переполниться. Я понятия не имею, как это сделать.

0 голосов
/ 18 октября 2019

Похоже, что ответ tfb позволяет немного уменьшить фрагмент кода:

(declaim (optimize (speed 2)))

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

(declaim (inline smallishtest))
(defun smallishtest (i j)
  (declare (type smallish-integer i j))
  (/ 1.0d0 (* (+ i j) (+ i j 1))))

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

: и при этом дать только одно примечание к компиляции:

; note: doing float to pointer coercion (cost 13) to "<return value>"

Затем уменьшается еще немного:

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

(declaim (inline smallishtest))
(defun smallishtest (i j)
  (declare (type smallish-integer i j))
  (/ 1.0d0 (* (+ i j) (+ i j 1))))

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