NextPrevUpTopContentsIndex

9.4 Compiler control

There are ways to control the nature of compiled code via the declare special form and proclaim function. See later in this chapter for fuller discussion of these two forms.

In particular there are a set of optimize qualities which take integral values from 0 to 3, in order to control the trade-offs between code size, speed, compilation time, debuggability of the resulting code, and the safety of the code (whether type checks are omitted). For example:

(proclaim '(optimize (speed 3) (safety 0) (debug 0)))

tells the compiler to concentrate on code speed rather than anything else, and

(proclaim '(optimize (safety 3)))

ensures that the compiler never takes liberties with Lisp semantics and produces code that checks for every kind of signallable error.

The important declarations to the compiler are type declarations and optimize declarations. To declare that the type of the value of a variable can be relied upon to be unchanging (and hence allow the compiler to omit various checks in the code), say:

(declare (type the-type variable * )

Optimize declarations have various qualities, and these take values from 0 to 3. The names are safety , fixnum-safety , float , sys:interruptable , debug , speed , compilation-speed , and space .

Most of the qualities default to 1 (but safety and fixnum-safety default to 3 and interruptable defaults to 0). You can either associate an optimize quality with a new value (with local lexical scope if in declare , and global scope if proclaim ), or just give it by itself, which implies the value 3 (taken to mean "maximum" in some loose sense).

Thus you ensure code is at maximum safety by:

(proclaim '(optimize (safety 3)))

or

(proclaim '(optimize safety))

and reduce debugging information to a minimum by

(proclaim '(optimize (debug 0)))

The float declaration allows generation of more efficient code using float numbers. It reduces allocation during float calculations. It is best used with safety 0. For example:

(progn
  (setf a 
        (make-array 1000 
                    :initial-element 1D0
                    :element-type 'double-float))
  nil ; to avoid printing the large array
  )
 
(compile 
 (defun test (a)
   (declare (optimize (speed 3) (safety 0) (float 0)))
   (declare (type (simple-array double-float  (1000))
                  a))
   (let ((sum 0D0))
     (declare (type double-float sum))
     (dotimes (i 1000)
       (incf sum (the double-float (aref a i))))
     sum)))
 
(time (test a))
=>
Timing the evaluation of (TEST A)
 
user time    =      0.000
system time  =      0.000
Elapsed time =   0:00:00
Allocation   = 16 bytes standard / 0 bytes conses
0 Page faults

Normally code is interruptible, but when going for the extreme levels of speed and "undebuggability" this ceases to be the case unless you also ensure it thus:

(proclaim '(optimize (debug 0) (safety 0) (speed 3) interruptable))

The levels of safety have the following implications:

The levels of fixnum-safety have the following implications:

The effects of combining these qualities is summarized below:

Combining debug and safety levels in the compiler

Keyword settings

Operations

safety=0

Array access optimizations

debug>0

Dumps symbol names for arglist

debug>=2

Ensure debugger knows values of args (and variable when source debugging is on)

debug<1

Does not generate any debug info at all

debug=3

Avoids make-instance and find-class optimizations

debug=3

Avoids gethash and puthash optimizations

debug=3

Avoids ldb and dpb optimizations

debug=3

Avoids an optimization to last

safety>1

Be careful when multiple value counts are wrong

safety<1

Do not check array indices during write

safety<2

Do not check array indices during read

debug<3

Eliminate tail recursion

speed>space

Inline map functions (unless debug>2 )

debug<=2

Do tail merging

debug<2 and safety<2

Self calls

safety>=2

Check get special

safety<2

Do not check types during write

safety<3

Do not check types during read

safety>=1

Check structure access

debug>=1

Call count count

safety>1

Check number of args

safety>=1 or interruptible>0

Check stack overflow

safety>1

Ensures the thing being funcalled is a function

safety<3 and fixnum-safety=2

Fixnum-only arithmetic with errors for non fixnum arguments.

safety<3 and fixnum-safety=1

No fixnum overflow checks

safety<3 and fixnum-safety=0

No fixnum arithmetic checks at all

safety>2

char= checks for arguments of type character

safety>=2

Ensures symbols in progv

debug=3

Avoids "ad hoc" predicate type transforms

compilation-speed=3

Reuse virtual registers in very large functions

debug=3 and safety=3

(declare (type foo x)) and
(the foo x) ensure a type check

float=0

Optimize floating point calculations.

The other optimize qualities are: speed -- the attention to fast code, space -- the degree of compactness, compilation-speed -- speed of compilation, interruptable -- whether code must be interruptible when unsafe.

Note that if you compile code with a low level of safety, you may get segmentation violations if the code is incorrect (for example, if type checking is turned off and you supply incorrect types). You can check this by interpreting the code rather than compiling it.

For example, the following function, compiled with safety = 2, does not check the type of its argument because it merely reads:

(defun foo (x)
  (declare (optimize (safety 2)))
  (car x))

However the following function, also compiled with safety = 2, does check the type of its argument because it writes:

(defun set-foo (x y)
  (declare (optimize (safety 2)))
  (setf (car x) y))

LispWorks User Guide - 8 Apr 2005

NextPrevUpTopContentsIndex