4 ms·
You're right that this is not a fair comparison (I was mostly interested in showing a variant with loop that is guaranteed to work on all implementations, unlik
by junke 6y ago
You're right that this is not a fair comparison (I was mostly interested in showing a variant with loop that is guaranteed to work on all implementations, unlike fact2).
Without type declaration, and without speed 3 optimization, the fact3 case is:
Evaluation took:
2.628 seconds of real time
2.624228 seconds of total run time (2.624228 user, 0.000000 system)
99.85% CPU
8,395,768,934 processor cycles
0 bytes consed
Still less that fact2.
But fact2 optimized and with types is:
Evaluation took:
1.416 seconds of real time
1.419171 seconds of total run time (1.419098 user, 0.000073 system)
100.21% CPU
4,530,124,009 processor cycles
0 bytes consed
For reference, here are the dissassembly for both optimized versions.
; disassembly for FACT2
; Size: 97 bytes. Origin: #x5380C48A ; FACT2
; 8A: 498B4D10 MOV RCX, [R13+16] ; thread.binding-stack-pointer
; 8E: 48894DF8 MOV [RBP-8], RCX
; 92: BB02000000 MOV EBX, 2
; 97: 660F1F840000000000 NOP
; A0: L0: 4885C0 TEST RAX, RAX
; A3: 743B JEQ L1
; A5: 48895DE8 MOV [RBP-24], RBX
; A9: 488945E0 MOV [RBP-32], RAX
; AD: BF02000000 MOV EDI, 2
; B2: 488BD0 MOV RDX, RAX
; B5: E846527FFE CALL #x52001700 ; GENERIC--
; BA: 488BF2 MOV RSI, RDX
; BD: 488B45E0 MOV RAX, [RBP-32]
; C1: 488B5DE8 MOV RBX, [RBP-24]
; C5: 488975F0 MOV [RBP-16], RSI
; C9: 488BD0 MOV RDX, RAX
; CC: 488BFB MOV RDI, RBX
; CF: E88C527FFE CALL #x52001760 ; GENERIC-*
; D4: 488B75F0 MOV RSI, [RBP-16]
; D8: 488BC6 MOV RAX, RSI
; DB: 488BDA MOV RBX, RDX
; DE: EBC0 JMP L0
; E0: L1: 488BD3 MOV RDX, RBX
; E3: 488BE5 MOV RSP, RBP
; E6: F8 CLC
; E7: 5D POP RBP
; E8: C3 RET
; E9: CC10 INT3 16 ; Invalid argument count trap
And
; disassembly for FACT3
; Size: 113 bytes. Origin: #x5380DA07 ; FACT3
; 07: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer
; 0B: 488945F8 MOV [RBP-8], RAX
; 0F: 31F6 XOR ESI, ESI
; 11: 31C0 XOR EAX, EAX
; 13: 488BF2 MOV RSI, RDX
; 16: B802000000 MOV EAX, 2
; 1B: EB31 JMP L1
; 1D: 0F1F00 NOP
; 20: L0: 488945E8 MOV [RBP-24], RAX
; 24: BF02000000 MOV EDI, 2
; 29: 488BD6 MOV RDX, RSI
; 2C: E8CF3C7FFE CALL #x52001700 ; GENERIC--
; 31: 488BF2 MOV RSI, RDX
; 34: 488B45E8 MOV RAX, [RBP-24]
; 38: 488975F0 MOV [RBP-16], RSI
; 3C: 488BD6 MOV RDX, RSI
; 3F: 488BF8 MOV RDI, RAX
; 42: E8193D7FFE CALL #x52001760 ; GENERIC-*
; 47: 488B75F0 MOV RSI, [RBP-16]
; 4B: 488BC2 MOV RAX, RDX
; 4E: L1: 488BDE MOV RBX, RSI
; 51: F6C301 TEST BL, 1
; 54: 7507 JNE L2
; 56: 4885DB TEST RBX, RBX
; 59: 7FC5 JNLE L0
; 5B: EB10 JMP L3
; 5D: L2: 488B5EF1 MOV RBX, [RSI-15]
; 61: 48C1EB08 SHR RBX, 8
; 65: 48837CDEF100 CMP QWORD PTR [RSI+RBX*8-15], 0
; 6B: 7DB3 JNL L0
; 6D: L3: 488BD0 MOV RDX, RAX
; 70: 488BE5 MOV RSP, RBP
; 73: F8 CLC
; 74: 5D POP RBP
; 75: C3 RET
The code after expansion of the loop is a tagbody (labels and goto statements) which calls setq on local variables, whereas the tail-call recursive case is not expanded; the control-flow graph might be easier to optimize given that LABELS is a special form.
- bjoli 6y agoI come from scheme land, where that internal tail recursive function is the way to go for speed (internal definitions are easy to verify that they are never changed, which allows to skip a lookup - the same should be true for CL). I thought the same would be true for SBCL, but apparently it needed some nudging.