The less-familiar parts of Lisp for beginners — declare

We’ve just discussed declaim, and now we go on to declare.  This is a way to provide guidance to the interpreter/compiler, but limited in scope to the form in which they declare directive appears.  While, syntactically, declare looks like a function or macro, it is a logically distinct entity.  In forms where declare is valid, it must appear before any of the expressions that “do something”.  The beginning Lisp programmer is likely to come across a few contexts where the declare is useful:

  • To set/change the optimization settings on a per-function basis
  • To provide hints to the optimizer, improving code speed
  • To suppress warnings

As I mentioned when discussing declaim, the scope of optimization settings assigned with declaim is not specified.  They compiler may or may not retain those settings across multiple compilation units.  In contrast, declare is defined to have a scope limited to the form in which it appears.

To go over some uses of declare, we’ll look at a simple random number generator, an old Linear Congruential Generator from glibc.  This can be coded as follows:
 

(defparameter *rand-state* 0)
(defparameter *rand-modulus* #x80000000)
(defparameter *rand-multiplier* 1103515245)
(defparameter *rand-increment* 12345)

(defun set-rand-state (val)
  (setf *rand-state* val))

(defun get-next-rand ()
  (setf *rand-state*
        (mod (+ (* *rand-multiplier* *rand-state*)
                *rand-increment*)
             *rand-modulus*)))

Now, if this code is part of a larger project, you might be debugging that project, and so want to have debugging-compatible optimizations.  Meanwhile, you’re confident of your random-number implementation, and it turns out to have a noticeable performance impact, so you want to optimize just that function.  Seems unlikely that such a simple function could affect runtime, I know, but it could happen.

Aside:  I remember in one project coming across code which tried to generate a random number with a Gaussian distribution, and did it by drawing 20 uniform random numbers in a row and averaging them, counting on the fact that the Bates distribution looks Gaussian when N gets large enough.  The RNG turned out to consume a noticeable fraction of the total runtime of the program.

OK, so you’ve decided to optimize the random number generator function, but leave all other functions in the source file to have their own optimization settings.  We change the get-next-rand function to read like this:

 
(defun get-next-rand ()
  (declare (optimize (speed 3) (debug 0) (safety 0)))
  (setf *rand-state*
        (mod (+ (* *rand-multiplier* *rand-state*)
                *rand-increment*)
             *rand-modulus*)))

Now, you compile the .lisp file in SBCL, and it produces output like this:
 

; file: opt.lisp
; in: DEFUN GET-NEXT-RAND
;     (* *RAND-MULTIPLIER* *RAND-STATE*)
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a RATIONAL.
;   The second argument is a NUMBER, not a FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a FLOAT.
;   The second argument is a NUMBER, not a RATIONAL.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a SINGLE-FLOAT.
;   The second argument is a NUMBER, not a DOUBLE-FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a DOUBLE-FLOAT.
;   The second argument is a NUMBER, not a SINGLE-FLOAT.
; 
; note: unable to
;   convert x*2^k to shift
; due to type uncertainty:
;   The first argument is a NUMBER, not a INTEGER.
;   The second argument is a NUMBER, not a INTEGER.

;     (+ (* *RAND-MULTIPLIER* *RAND-STATE*) *RAND-INCREMENT*)
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a RATIONAL.
;   The second argument is a NUMBER, not a FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a FLOAT.
;   The second argument is a NUMBER, not a RATIONAL.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a SINGLE-FLOAT.
;   The second argument is a NUMBER, not a DOUBLE-FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a NUMBER, not a DOUBLE-FLOAT.
;   The second argument is a NUMBER, not a SINGLE-FLOAT.

;     (MOD (+ (* *RAND-MULTIPLIER* *RAND-STATE*) *RAND-INCREMENT*) *RAND-MODULUS*)
; --> BLOCK LET IF AND IF NOT IF ZEROP 
; ==>
;   (= REM 0)
; 
; note: unable to
;   open-code FLOAT to RATIONAL comparison
; due to type uncertainty:
;   The first argument is a REAL, not a FLOAT.
; 
; note: unable to open code because: The operands might not be the same type.

; --> BLOCK LET IF AND IF AND THE IF MINUSP 
; ==>
;   (< SB-KERNEL::DIVISOR 0)
; 
; note: unable to
;   open-code FLOAT to RATIONAL comparison
; due to type uncertainty:
;   The first argument is a REAL, not a FLOAT.

; --> BLOCK LET IF AND IF AND THE IF PLUSP 
; ==>
;   (> NUMBER 0)
; 
; note: unable to
;   open-code FLOAT to RATIONAL comparison
; due to type uncertainty:
;   The first argument is a REAL, not a FLOAT.

; --> BLOCK LET IF AND IF AND THE IF MINUSP 
; ==>
;   (< NUMBER 0)
; 
; note: unable to
;   open-code FLOAT to RATIONAL comparison
; due to type uncertainty:
;   The first argument is a REAL, not a FLOAT.

; --> BLOCK LET IF 
; ==>
;   (+ REM SB-KERNEL::DIVISOR)
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a RATIONAL.
;   The second argument is a REAL, not a FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a FLOAT.
;   The second argument is a REAL, not a RATIONAL.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a SINGLE-FLOAT.
;   The second argument is a REAL, not a DOUBLE-FLOAT.
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a DOUBLE-FLOAT.
;   The second argument is a REAL, not a SINGLE-FLOAT.

; --> BLOCK LET REM BLOCK MULTIPLE-VALUE-BIND MULTIPLE-VALUE-CALL 
; ==>
;   (TRUNCATE NUMBER SB-KERNEL::DIVISOR)
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a SINGLE-FLOAT.
;   The second argument is a REAL, not a (OR SINGLE-FLOAT INTEGER).
; 
; note: unable to
;   optimize
; due to type uncertainty:
;   The first argument is a REAL, not a DOUBLE-FLOAT.
;   The second argument is a REAL, not a (OR DOUBLE-FLOAT SINGLE-FLOAT INTEGER).
; 
; note: unable to
;   convert division by 2^k to shift
; due to type uncertainty:
;   The first argument is a REAL, not a INTEGER.
;   The second argument is a REAL, not a INTEGER.

;     (* *RAND-MULTIPLIER* *RAND-STATE*)
; 
; note: forced to do GENERIC-* (cost 30)
;       unable to do inline float arithmetic (cost 4) because:
;       The first argument is a NUMBER, not a (COMPLEX SINGLE-FLOAT).
;       The second argument is a NUMBER, not a SINGLE-FLOAT.
;       The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES
;                                                         (COMPLEX SINGLE-FLOAT)
;                                                         &REST T).
;       unable to do inline float arithmetic (cost 4) because:
;       The first argument is a NUMBER, not a SINGLE-FLOAT.
;       The second argument is a NUMBER, not a SINGLE-FLOAT.
;       The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES SINGLE-FLOAT
;                                                                &REST T).
;       etc.

;     (+ (* *RAND-MULTIPLIER* *RAND-STATE*) *RAND-INCREMENT*)
; 
; note: forced to do GENERIC-+ (cost 10)
;       unable to do inline float arithmetic (cost 2) because:
;       The first argument is a NUMBER, not a DOUBLE-FLOAT.
;       The second argument is a NUMBER, not a DOUBLE-FLOAT.
;       The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES DOUBLE-FLOAT
;                                                                &REST T).
;       unable to do inline float arithmetic (cost 2) because:
;       The first argument is a NUMBER, not a SINGLE-FLOAT.
;       The second argument is a NUMBER, not a SINGLE-FLOAT.
;       The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES SINGLE-FLOAT
;                                                                &REST T).
;       etc.

;     (MOD (+ (* *RAND-MULTIPLIER* *RAND-STATE*) *RAND-INCREMENT*) *RAND-MODULUS*)
; --> BLOCK LET IF AND IF AND THE IF MINUSP 
; ==>
;   (< SB-KERNEL::DIVISOR 0)
; 
; note: forced to do GENERIC-< (cost 10)
;       unable to do inline fixnum comparison (cost 3) because:
;       The first argument is a REAL, not a FIXNUM.
;       unable to do inline fixnum comparison (cost 4) because:
;       The first argument is a REAL, not a FIXNUM.
;       etc.

; ==>
;   (< NUMBER 0)
; 
; note: forced to do GENERIC-< (cost 10)
;       unable to do inline fixnum comparison (cost 3) because:
;       The first argument is a REAL, not a FIXNUM.
;       unable to do inline fixnum comparison (cost 4) because:
;       The first argument is a REAL, not a FIXNUM.
;       etc.

; --> BLOCK LET IF AND IF AND THE IF PLUSP 
; ==>
;   (> NUMBER 0)
; 
; note: forced to do GENERIC-> (cost 10)
;       unable to do inline fixnum comparison (cost 3) because:
;       The first argument is a REAL, not a FIXNUM.
;       unable to do inline fixnum comparison (cost 4) because:
;       The first argument is a REAL, not a FIXNUM.
;       etc.

; --> BLOCK LET IF 
; ==>
;   (+ REM SB-KERNEL::DIVISOR)
; 
; note: forced to do GENERIC-+ (cost 10)
;       unable to do inline float arithmetic (cost 2) because:
;       The first argument is a REAL, not a DOUBLE-FLOAT.
;       The second argument is a REAL, not a DOUBLE-FLOAT.
;       The result is a (VALUES REAL &OPTIONAL), not a (VALUES DOUBLE-FLOAT &REST
;                                                              T).
;       unable to do inline float arithmetic (cost 2) because:
;       The first argument is a REAL, not a SINGLE-FLOAT.
;       The second argument is a REAL, not a SINGLE-FLOAT.
;       The result is a (VALUES REAL &OPTIONAL), not a (VALUES SINGLE-FLOAT &REST
;                                                              T).
;       etc.

The optimizer is not happy.  It’s pointing out that it does not have type knowledge for the parameters of the algorithm, so it has to use its most generic settings, and run-time typing.  As the C++ programmer coming to Lisp, you routinely declared the type of every single variable in your program, because of the strong typing in that language.  You would have declared these parameters to be of type uint_fast32_t, and trusted the compiler to optimize the code appropriately.  In Lisp, you are not forced to tell the compiler about every variable type, but in a situation like this, it helps to do so.  You can use declare to hint to the interpreter/compiler what the types are of certain variables in your expressions.  What we do is to tell the optimizer that all of those numbers are 31-bit integers, and some of them will never be zero.  Our code now looks like this:
 

(defun get-next-rand ()
  (declare (optimize (speed 3) (debug 0) (safety 0)))
  (declare ((integer 0 #x80000000) *rand-state*))
  (declare ((integer 1 #x80000000) *rand-multiplier*))
  (declare ((integer 1 #x80000000) *rand-increment*))
  (declare ((integer 1 #x80000000) *rand-modulus*))
  (setf *rand-state*
        (mod (+ (* *rand-multiplier* *rand-state*)
                *rand-increment*)
             *rand-modulus*)))

When this is compiled, the optimizer produces no diagnostic output at all.  We’ve given it everything it needs to generate optimized code for this case.

Now, you might ask, does it make a difference?  I touched on this in an earlier set of posts, but I’ll go over it again here.  Let’s look at the performance difference between the optimized code with the type declarations, and the optimized code without the type declarations. EDIT #2 on 2014-10-18 to add: For purposes of clarity while typing in the REPL, I will call this latest version of the code, with the five declare forms, get-next-rand-declared.
 

CL-USER> (time (dotimes (i 1000000) (get-next-rand)))
Evaluation took:
  0.026 seconds of real time
  0.028002 seconds of total run time (0.028002 user, 0.000000 system)
  107.69% CPU
  86,409,936 processor cycles
  0 bytes consed

NIL
CL-USER> (time (dotimes (i 1000000) (get-next-rand-declared)))
Evaluation took:
  0.015 seconds of real time
  0.020001 seconds of total run time (0.020001 user, 0.000000 system)
  133.33% CPU
  51,911,523 processor cycles
  0 bytes consed

NIL

Look just at the processor cycles lines.  The version with declared variable took 52 million cycles, vs. over 86 million for the type with unknown types.

If you’re interested, here’s the disassembly of the two functions:
 

CL-USER> (disassemble 'get-next-rand)
; disassembly for GET-NEXT-RAND
; 0587651F:       488B058AFFFFFF   MOV RAX, [RIP-118]         ; '*RAND-MULTIPLIER*
                                                              ; no-arg-parsing entry point
;      526:       488B5021         MOV RDX, [RAX+33]
;      52A:       498B1414         MOV RDX, [R12+RDX]
;      52E:       4883FA61         CMP RDX, 97
;      532:       7504             JNE L0
;      534:       488B50F9         MOV RDX, [RAX-7]
;      538: L0:   488B0579FFFFFF   MOV RAX, [RIP-135]         ; '*RAND-STATE*
;      53F:       488B7821         MOV RDI, [RAX+33]
;      543:       498B3C3C         MOV RDI, [R12+RDI]
;      547:       4883FF61         CMP RDI, 97
;      54B:       7504             JNE L1
;      54D:       488B78F9         MOV RDI, [RAX-7]
;      551: L1:   4C8D1C25B9020020 LEA R11, [#x200002B9]      ; GENERIC-*
;      559:       41FFD3           CALL R11
;      55C:       480F42E3         CMOVB RSP, RBX
;      560:       488B0559FFFFFF   MOV RAX, [RIP-167]         ; '*RAND-INCREMENT*
;      567:       488B7821         MOV RDI, [RAX+33]
;      56B:       498B3C3C         MOV RDI, [R12+RDI]
;      56F:       4883FF61         CMP RDI, 97
;      573:       7504             JNE L2
;      575:       488B78F9         MOV RDI, [RAX-7]
;      579: L2:   4C8D1C25E0010020 LEA R11, [#x200001E0]      ; GENERIC-+
;      581:       41FFD3           CALL R11
;      584:       480F42E3         CMOVB RSP, RBX
;      588:       488955F8         MOV [RBP-8], RDX
;      58C:       488B0D35FFFFFF   MOV RCX, [RIP-203]         ; '*RAND-MODULUS*
;      593:       488B4121         MOV RAX, [RCX+33]
;      597:       498B0404         MOV RAX, [R12+RAX]
;      59B:       4883F861         CMP RAX, 97
;      59F:       7504             JNE L3
;      5A1:       488B41F9         MOV RAX, [RCX-7]
;      5A5: L3:   488945E8         MOV [RBP-24], RAX
;      5A9:       488B55F8         MOV RDX, [RBP-8]
;      5AD:       488B7DE8         MOV RDI, [RBP-24]
;      5B1:       488D5C24F0       LEA RBX, [RSP-16]
;      5B6:       4883EC18         SUB RSP, 24
;      5BA:       488B050FFFFFFF   MOV RAX, [RIP-241]         ; #<FDEFINITION object for TRUNCATE>
;      5C1:       B904000000       MOV ECX, 4
;      5C6:       48892B           MOV [RBX], RBP
;      5C9:       488BEB           MOV RBP, RBX
;      5CC:       FF5009           CALL QWORD PTR [RAX+9]
;      5CF:       48897DF0         MOV [RBP-16], RDI
;      5D3:       488B55F0         MOV RDX, [RBP-16]
;      5D7:       31FF             XOR EDI, EDI
;      5D9:       488D0C2586040020 LEA RCX, [#x20000486]      ; GENERIC-=
;      5E1:       FFD1             CALL RCX
;      5E3:       7529             JNE L8
;      5E5: L4:   488B4DF0         MOV RCX, [RBP-16]
;      5E9: L5:   488B15C8FEFFFF   MOV RDX, [RIP-312]         ; '*RAND-STATE*
;      5F0:       488B4221         MOV RAX, [RDX+33]
;      5F4:       49833C0461       CMP QWORD PTR [R12+RAX], 97
;      5F9:       7406             JEQ L6
;      5FB:       49890C04         MOV [R12+RAX], RCX
;      5FF:       EB04             JMP L7
;      601: L6:   48894AF9         MOV [RDX-7], RCX
;      605: L7:   488BD1           MOV RDX, RCX
;      608:       488BE5           MOV RSP, RBP
;      60B:       F8               CLC
;      60C:       5D               POP RBP
;      60D:       C3               RET
;      60E: L8:   488B55E8         MOV RDX, [RBP-24]
;      612:       31FF             XOR EDI, EDI
;      614:       488D0C25E5030020 LEA RCX, [#x200003E5]      ; GENERIC-<
;      61C:       FFD1             CALL RCX
;      61E:       7D2E             JNL L10
;      620:       488B55F8         MOV RDX, [RBP-8]
;      624:       31FF             XOR EDI, EDI
;      626:       488D0C251B040020 LEA RCX, [#x2000041B]      ; GENERIC->
;      62E:       FFD1             CALL RCX
;      630:       7EB3             JLE L4
;      632: L9:   488B55F0         MOV RDX, [RBP-16]
;      636:       488B7DE8         MOV RDI, [RBP-24]
;      63A:       4C8D1C25E0010020 LEA R11, [#x200001E0]      ; GENERIC-+
;      642:       41FFD3           CALL R11
;      645:       480F42E3         CMOVB RSP, RBX
;      649:       488BCA           MOV RCX, RDX
;      64C:       EB9B             JMP L5
;      64E: L10:  488B55F8         MOV RDX, [RBP-8]
;      652:       31FF             XOR EDI, EDI
;      654:       488D0C25E5030020 LEA RCX, [#x200003E5]      ; GENERIC-<
;      65C:       FFD1             CALL RCX
;      65E:       7CD2             JL L9
;      660:       EB83             JMP L4
NIL
CL-USER> (disassemble 'get-next-rand-declared)
; disassembly for GET-NEXT-RAND-DECLARED
; 058B4AFF:       488B0D9AFFFFFF   MOV RCX, [RIP-102]         ; '*RAND-MULTIPLIER*
                                                              ; no-arg-parsing entry point
;      B06:       488B4121         MOV RAX, [RCX+33]
;      B0A:       498B0404         MOV RAX, [R12+RAX]
;      B0E:       4883F861         CMP RAX, 97
;      B12:       7504             JNE L0
;      B14:       488B41F9         MOV RAX, [RCX-7]
;      B18: L0:   488B1589FFFFFF   MOV RDX, [RIP-119]         ; '*RAND-STATE*
;      B1F:       488B4A21         MOV RCX, [RDX+33]
;      B23:       498B0C0C         MOV RCX, [R12+RCX]
;      B27:       4883F961         CMP RCX, 97
;      B2B:       7504             JNE L1
;      B2D:       488B4AF9         MOV RCX, [RDX-7]
;      B31: L1:   48D1F8           SAR RAX, 1
;      B34:       48D1F9           SAR RCX, 1
;      B37:       488BD0           MOV RDX, RAX
;      B3A:       480FAFD1         IMUL RDX, RCX
;      B3E:       488B0D6BFFFFFF   MOV RCX, [RIP-149]         ; '*RAND-INCREMENT*
;      B45:       488B4121         MOV RAX, [RCX+33]
;      B49:       498B0404         MOV RAX, [R12+RAX]
;      B4D:       4883F861         CMP RAX, 97
;      B51:       7504             JNE L2
;      B53:       488B41F9         MOV RAX, [RCX-7]
;      B57: L2:   488BC8           MOV RCX, RAX
;      B5A:       48D1F9           SAR RCX, 1
;      B5D:       488D040A         LEA RAX, [RDX+RCX]
;      B61:       488B1550FFFFFF   MOV RDX, [RIP-176]         ; '*RAND-MODULUS*
;      B68:       488B4A21         MOV RCX, [RDX+33]
;      B6C:       498B0C0C         MOV RCX, [R12+RCX]
;      B70:       4883F961         CMP RCX, 97
;      B74:       7504             JNE L3
;      B76:       488B4AF9         MOV RCX, [RDX-7]
;      B7A: L3:   48D1F9           SAR RCX, 1
;      B7D:       4885C9           TEST RCX, RCX
;      B80:       742A             JEQ L6
;      B82:       4899             CQO
;      B84:       48F7F9           IDIV RAX, RCX
;      B87:       48D1E2           SHL RDX, 1
;      B8A:       488B0D17FFFFFF   MOV RCX, [RIP-233]         ; '*RAND-STATE*
;      B91:       488B4121         MOV RAX, [RCX+33]
;      B95:       49833C0461       CMP QWORD PTR [R12+RAX], 97
;      B9A:       7406             JEQ L4
;      B9C:       49891404         MOV [R12+RAX], RDX
;      BA0:       EB04             JMP L5
;      BA2: L4:   488951F9         MOV [RCX-7], RDX
;      BA6: L5:   488BE5           MOV RSP, RBP
;      BA9:       F8               CLC
;      BAA:       5D               POP RBP
;      BAB:       C3               RET
;      BAC: L6:   CC0A             BREAK 10                   ; error trap
;      BAE:       03               BYTE #X03
;      BAF:       1E               BYTE #X1E                  ; DIVISION-BY-ZERO-ERROR
;      BB0:       18               BYTE #X18                  ; RAX
;      BB1:       58               BYTE #X58                  ; RCX
NIL

You’ll note that, with the declare directives, the math all takes place in the function definition, while without them, the function makes multiple calls into generic arithmetic functions that will have to determine the type at runtime and perform the appropriate operations.

OK, so that covers the compiler settings and optimizer hints.  I mentioned a third use, suppressing warnings.  As a programmer, I always want my code to compile without warnings.  It makes the build less noisy, it makes it easier to find real errors due to recent changes, and warnings can sometimes point out typos or erroneous code that, while syntactically correct, is suspicious enough to warrant investigation.  So, when might declare be used to suppress warnings? Using our random number generator example, maybe we changed the API. We used to pass the previous random number in to the function, so that we didn’t have to maintain a separate state variable, or so that we could run several independent streams. Later, we decided to eliminate this behaviour, but didn’t want to go to change all of the instances where the random number function was called.  So, the parameter suddenly became unused.  It looks like this now:
 

(defun get-next-rand (previous-number)
  (setf *rand-state*
        (mod (+ (* *rand-multiplier* *rand-state*)
                *rand-increment*)
             *rand-modulus*)))

However, when you compile this file,  you get a warning from SBCL:
 

; file: /home/neufeld/programming/lisp/blogging/opt.lisp
; in: DEFUN GET-NEXT-RAND
;     (DEFUN GET-NEXT-RAND (PREVIOUS-NUMBER)
;       (SETF *RAND-STATE* (MOD (+ # *RAND-INCREMENT*) *RAND-MODULUS*)))
; --> PROGN EVAL-WHEN 
; ==>
;   (SB-IMPL::%DEFUN 'GET-NEXT-RAND
;                    (SB-INT:NAMED-LAMBDA GET-NEXT-RAND
;                        (PREVIOUS-NUMBER)
;                      (BLOCK GET-NEXT-RAND (SETF *RAND-STATE* #)))
;                    NIL 'NIL (SB-C:SOURCE-LOCATION))
; 
; caught STYLE-WARNING:
;   The variable PREVIOUS-NUMBER is defined but never used.

; compiling (DEFUN GET-NEXT-RAND-DECLARED ...); 
                                              ; compilation unit finished
                                              ;   caught 1 STYLE-WARNING condition

The compiler is warning you of an unused variable.  To eliminate the warning, and get a nice clean screen on your compiles, you tell the compiler that yes, you know this variable is unused, that’s fine.
 

(defun get-next-rand (previous-number)
  (declare (ignore previous-number))
  (setf *rand-state*
        (mod (+ (* *rand-multiplier* *rand-state*)
                *rand-increment*)
             *rand-modulus*)))

Another possibility is to declare a variable as ignorable.  That is it might or might not be used in the form.  This can happen as you write specialized macros that can determine at compile-time whether or not to inline certain code, but this is already a long enough posting so I’m going to wrap it up here.

EDIT #1 on 2014-10-18 to fix an unbalanced “raw” tag in place of “/raw” in some quoted code.

6 thoughts on “The less-familiar parts of Lisp for beginners — declare

  1. The function get-next-rand-declared is not in this post above. The optimized get-next-rand described above is missing optimization options.

    1. Sorry for the lack of clarity. The get-next-rand-declared function is simply the version with the five declare forms in it. I thought the command-line invocation would be confusing if I timed the same function name twice, so for purposes of the demonstration I renamed the function in my REPL, while leaving it the same in the posting. The comparison I provide is between the function without declare forms and with. I’ll edit the text to make that clear.

      You say that the optimized function is missing optimization options. What more optimizations would you expect?

  2. I think some declarations for the returned values, for example:

    (defun add-1 (x y)
    (declare (optimize (speed 3) (debug 0) (safety 0))
    (type fixnum x y))
    (+ x y))

    (disassemble ‘add-1)

    ; disassembly for ADD-1
    ; 0B857E58: 8D043A LEA EAX, [EDX+EDI] ; no-arg-parsing entry point
    ; 5B: 6BD004 IMUL EDX, EAX, 4
    ; 5E: 7107 JNO L0
    ; 60: 8BD0 MOV EDX, EAX
    ; 62: E85E877AF5 CALL #x10005C5 ; ALLOC-SIGNED-BIGNUM-IN-EDX
    ; 67: L0: 8BE5 MOV ESP, EBP
    ; 69: F8 CLC
    ; 6A: 5D POP EBP
    ; 6B: C3 RET

    (defun add-2 (x y)
    (declare (optimize (speed 3) (debug 0) (safety 0))
    (type fixnum x y))
    (the fixnum (+ x y)))

    (disassemble ‘add-2)

    ; disassembly for ADD-2
    ; 0B86F492: 01FA ADD EDX, EDI ; no-arg-parsing entry point
    ; 4: 8BE5 MOV ESP, EBP
    ; 6: F8 CLC
    ; 7: 5D POP EBP
    ; 8: C3 RET

    1. francogrex:

      Yes, that is another optimization, one that I discuss in the later article about the the special operator. As this post was about declare, and not about the more general topic of optimizations in Common Lisp, I didn’t use it here.

      Thank you, though, for pointing out that optimizations don’t begin and end with declare, there are other features that also help.

  3. Thanks to you Christopher for the blog and the information about Common Lisp, I (and very likely many other readers) enjoy and learn a lot from it. Best Regards.

Leave a Reply to francogrex Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.