Skip to content
Type Declarations and Optimization
step 1/5

Reading — step 1 of 5

Learn

~2 min readOptimization, Concurrency, Idioms

Common Lisp is dynamically typed by default — but you can declare types and optimization levels and the compiler will produce code competitive with C.

Optimize declarations

(defun fast-sum (n)
    (declare (optimize (speed 3) (safety 0) (debug 0))
             (type fixnum n))
    (let ((sum 0))
        (declare (type fixnum sum))
        (dotimes (i n sum)
            (incf sum i))))

Qualities (0-3):

  • speed — runtime performance
  • safety — runtime checks (default 1)
  • space — code/data size
  • debug — debuggability (default 1)
  • compilation-speed — compile time

(speed 3) (safety 0) — full speed, no checks. Use only after profiling.

Type declarations

(declare (type fixnum n))                       ; small int (single machine word)
(declare (type integer n))                      ; arbitrary precision
(declare (type single-float x))                  ; 32-bit float
(declare (type double-float x))                  ; 64-bit float
(declare (type (simple-array fixnum (*)) arr))   ; specialized array
(declare (type (vector double-float) v))
(declare (type string s))

The compiler uses these to:

  • Skip dispatch / boxing
  • Inline arithmetic
  • Use unboxed array storage

Fixnum vs integer: fixnums fit in a machine word (typically up to ~2^61). Integer is arbitrary precision but slower. For tight loops with bounded values, fixnum is dramatically faster.

disassemble

See what the compiler generated:

(disassemble 'fast-sum)

For SBCL output is x86-64 assembly. Useful for understanding what (speed 3) actually buys you.

Inlining

(declaim (inline distance-squared))

(defun distance-squared (x1 y1 x2 y2)
    (declare (type single-float x1 y1 x2 y2)
             (optimize (speed 3) (safety 0)))
    (let ((dx (- x2 x1))
          (dy (- y2 y1)))
        (+ (* dx dx) (* dy dy))))

The declaim inline tells the compiler to inline calls. Crucial for hot small functions.

Reading the compiler

SBCL emits notes — pay attention:

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

This means SBCL had to box a float — slow. Adding a return-type declaration usually fixes it.

Specialized arrays

Generic arrays box every element. Specialized arrays don't:

(make-array 1000 :element-type '(unsigned-byte 8))   ; byte array, no boxing
(make-array 1000 :element-type 'fixnum)              ; packed fixnums
(make-array 1000 :element-type 'double-float)        ; packed doubles

Tail calls

Unlike Scheme, Common Lisp doesn't guarantee tail-call optimization — but most modern implementations (SBCL, CCL) do it for self-tail-calls and mutually-recursive ones. For portable code, prefer loop or do over deeply-recursive functions.

Profile before optimizing

The golden rule. SBCL has sb-profile:

(sb-profile:profile do-thing helper)
(my-app:run)
(sb-profile:report)

Reports per-function call counts and time. Optimize the slowest one first; ignore the rest.

Practical advice

  • 80/20: declare types in the 5 hot functions; leave the rest dynamic
  • optimize (speed 3) (safety 1) is usually a sweet spot
  • Use time macro: (time (expensive-thing)) reports CPU + memory
  • Premature optimization is still the root of evil — write correct code first, profile, then speed up the bottlenecks

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…