#:g1: compiled-functionという型

Posted 2021-06-13 21:02:13 GMT

Common Lispにはcompiled-functionというコンパイル済みの関数という変った型がありますが、あまり活用されていないマイナー機能のためか最適化の指示等で使うと、最適化されそうな型名とは裏腹に最適化されないことがあったりします。

下記は現時点での最新のSBCL 2.1.5の例ですが、compiled-functionで型指定すると遅くなります。

(declaim (ftype (function (fixnum) fixnum) loop/function loop/compiled-function))

(defun loop/function (n) (declare (optimize (speed 3) (debug 0) (safety 0))) (if (zerop n) 0 (funcall (the function #'loop/function) (1- n))))

(defun loop/compiled-function (n) (declare (optimize (speed 3) (debug 0) (safety 0))) (if (zerop n) 0 (funcall (the compiled-function #'loop/compiled-function) (1- n))))

(time (loop/function #.(expt 10 10))) Evaluation took: 2.830 seconds of real time 2.820000 seconds of total run time (2.820000 user, 0.000000 system) 99.65% CPU 9,310,557,922 processor cycles 0 bytes consed

(time (loop/compiled-function #.(expt 10 10))) Evaluation took: 27.800 seconds of real time 27.780000 seconds of total run time (27.780000 user, 0.000000 system) 99.93% CPU 91,499,786,739 processor cycles 0 bytes consed

disassembleすると、loop/compiled-functionの方は末尾呼び出しにはなっているものの、単純ループにまでは最適化されていないことが判ります。

; disassembly for LOOP/FUNCTION
; Size: 22 bytes. Origin: #x536BA8A0                          ; LOOP/FUNCTION
; A0: L0:   4885D2           TEST RDX, RDX
; A3:       7409             JEQ L1
; A5:       488D42FE         LEA RAX, [RDX-2]
; A9:       488BD0           MOV RDX, RAX
; AC:       EBF2             JMP L0
; AE: L1:   31D2             XOR EDX, EDX
; B0:       488BE5           MOV RSP, RBP
; B3:       F8               CLC
; B4:       5D               POP RBP
; B5:       C3               RET

; disassembly for LOOP/COMPILED-FUNCTION ; Size: 35 bytes. Origin: #x536BA9C6 ; LOOP/COMPILED-FUNCTION ; C6: 4885D2 TEST RDX, RDX ; C9: 7508 JNE L0 ; CB: 31D2 XOR EDX, EDX ; CD: 488BE5 MOV RSP, RBP ; D0: F8 CLC ; D1: 5D POP RBP ; D2: C3 RET ; D3: L0: 4883C2FE ADD RDX, -2 ; D7: 488B05C2FFFFFF MOV RAX, [RIP-62] ; #<FUNCTION LOOP/COMPILED-FUNCTION> ; DE: B902000000 MOV ECX, 2 ; E3: FF7508 PUSH QWORD PTR [RBP+8] ; E6: FF60FD JMP QWORD PTR [RAX-3]

恐らくSBCLの型推論のバグだと思いますが、報告するのも面倒で、発見から早六年経過してしまいました。
もしかすると、compiled-functionが再帰で使われた際に自分がコンパイル済みかどうかは判定が微妙というのもあるのかもしれません。
とはいえ、functionで最適化できているのだから特に問題なさそうですし、SBCLはコンパイラ指向なので関数は全部コンパイルされると見做しても良さそうでもあります。

一応他の処理系でも試してみましたが、LispWorksではcompiled-functionでも単純ループに最適化されました(fixnumの指定に若干の変更あり)

Disassembly of loop/compiled-function
4020001734:
       0:      4157             push  r15
       2:      55               push  rbp
       3:      4889E5           moveq rbp, rsp
       6:      4989DF           moveq r15, rbx
L1:    9:      4883FF00         cmpq  rdi, 0
      13:      750E             jne   L2
      15:      31FF             xor   edi, edi
      17:      B901000000       move  ecx, 1
      22:      4889EC           moveq rsp, rbp
      25:      5D               pop   rbp
      26:      415F             pop   r15
      28:      C3               ret   
L2:   29:      4883EF08         subq  rdi, 8
      33:      EBE6             jmp   L1
      35:      90               nop   

まとめ

SBCLの処理系のコードを追い掛けてみると案外ややこしいのに加え、compiled-functionの指定など誰も使わなそうなのでバグ報告に至っていません。
誰かとりまとめて報告してみてください……。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus