All Manuals > LispWorks® User Guide and Reference Manual > 9 The Compiler

9.7 Optimizing your code

Careful use of the compiler optimize qualities described above or special declarations may significantly improve the performance of your code. However it is not recommended that you simply experiment with the effect of adding declarations. It is more productive to work systematically:

  1. Use the Profiler, described in 12 The Profiler, to analyze your application's performance and identify bottlenecks, then:
  2. Consider whether re-writing of parts of your source code would improve efficiency at the bottlenecks, and:
  3. Use :explain declarations to make the compiler generate optimization hints, and:
  4. (In SMP LispWorks) use analyzing-special-variables-usage to report on symbols proclaimed special, and:
  5. Consider adding suitable declarations as described in this chapter to improve efficiency at the bottlenecks.

The most important tool for speeding up programs is the Profiler. You use the profiler to find the bottlenecks in the program, and then optimize these bottlenecks by helping the compiler to produce better code.

The remainder of this section describes some specific ways to produce efficient compiled code with LispWorks.

9.7.1 Compiler optimization hints

You can make the compiler print messages which will help you to optimize your code. You add suitable :explain declarations, recompile the code, and check the output.

The full syntax of the :explain declaration is documented in the reference entry for declare.

Various keywords allows you to see information about compiler transformations depending on type information, allocation of floats and bignums, floating point variables, function calls, argument types and so on. Here is a simple example:

(defun foo (arg)
  (declare (:explain :variables) (optimize (float 0)))
  (let* ((double-arg (coerce arg 'double-float))
         (next (+ double-arg 1d0))
         (other (* double-arg 1/2)))
    (values next other)))
;;- Variables with non-floating point types:
;;-  ARG OTHER
;;- Variables with floating point types:
;;-  DOUBLE-ARG NEXT

Note: the LispWorks IDE allows you to distinguish compiler optimization hints from the other output of compilation, and also helps you to locate quickly the source of each hint. For more information see the chapter "The Output Browser" in the LispWorks IDE User Guide.

9.7.2 Fast integer arithmetic

You can arrange for compiled code to perform optimal raw 32-bit arithmetic, and additionally in 64-bit LispWorks, optimal raw 64-bit arithmetic.

For all the details, see 28.2.2 Fast 32-bit arithmetic and 28.2.3 Fast 64-bit arithmetic.

9.7.3 Floating point optimization

The declaration float allows generation of more efficient code using float numbers. It reduces allocation during float calculations. It is best used with safety 0. That is, you declare (optimize (float 0) (safety 0)) as in this 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.000
Allocation   = 16 bytes
0 Page faults
GC time      =        0.000
1000.0D0

Note: In some cases, the operations cannot be fully optimized with float 0, which can cause the compiled code to be larger because the unboxing and boxing of floats will be inline.

9.7.4 Double-float complex number optimization

LispWorks will optimize operations on the type (complex double-float) when the declaration float is 0. For example:

(progn
  (setf a-complex
        (make-array 1000 
                    :initial-element #c(1D0 2D0)
                    :element-type '(complex double-float)))
  nil ; to avoid printing the large array
  )
 
(compile 
 (defun test-complex (a)
   (declare (optimize (speed 3) (safety 0) (float 0)))
   (declare (type (simple-array (complex double-float)  (1000))
                  a))
   (let ((sum #C(0D0 0D0)))
     (declare (type (complex double-float) sum))
     (dotimes (i 1000)
       (incf sum (the (complex double-float) (aref a i))))
     sum)))
 
(time (test-complex a-complex))
=>
Timing the evaluation of (TEST-COMPLEX A-COMPLEX)
 
User time    =        0.000
System time  =        0.000
Elapsed time =        0.000
Allocation   = 56 bytes
0 Page faults
GC time      =        0.000
#C(1000.0D0 2000.0D0)

9.7.5 Tail call optimization

The compiler optimizes tail calls, except in the following situations:

  1. The compiler optimize quality debug is 3.
  2. There is something with dynamic scope on the stack, such as a special binding, a catch or cl:dynamic-extent allocation (so it is not really a tail call).
  3. On 64-bit platforms, non-x86 platforms and non-ARM platforms, the call has more than 4 arguments and this is more than the number of fixed (not &optional/&rest/&key) parameters in the calling function.
  4. On 64-bit platforms, non-x86 platforms and non-ARM platforms, the call has more than 4 arguments and the calling function has &rest/&key parameters.

9.7.6 Usage of special variables

The declaration cl:special specifies that a variable is special, that is it does not have lexical scope. This covers two cases: if the variable is bound in the dynamic environment (for example by let or let*), then the value of that binding is used; otherwise the value in the global environment is used, if any. An error is signaled in safe code if there is no value in either environment. When setq is used with a variable, the value in the dynamic environment is modified if the variable is bound in the dynamic environment, otherwise the value in the global environment is modified. Dynamic variables can have a different value in each thread because each thread has is own dynamic environment. The global environment is shared between all threads.

In SMP LispWorks access to special variables (excluding constants) is a little slower than in non-SMP LispWorks. It can be made to run faster by declarations of the symbol, normally by using by proclaim or declaim, but also by declare.

The speedup will be pretty small overall in most cases, because access to specials is usually a small part of a program. However, if the Profiler identifies some piece of code as a bottleneck, you will want to optimize it, and your optimizations may include proclamation of some variable as global or dynamic.

The three declarations described in this section are extensions to Common Lisp. All declare the symbol to be cl:special, along with other information. These three declarations are mutually exclusive between themselves and cl:special. That is, declaring a symbol with any of these declarations eliminates the other declaration:

9.7.6.1 Finding symbols to declare

The macro analyzing-special-variables-usage can be used to find symbols that may be proclaimed global, which can improve performance. analyzing-special-variables-usage also helps to identify inconsistencies in the code.

9.7.6.2 Coalesce multiple special bindings

If a set of specials are always bound at the same time, it is better to store the values in a single structure object and bind one special variable to that object, to reduce the overall number of special bindings.

9.7.7 Stack allocation of objects with dynamic extent

(declare dynamic-extent) will optimize these calls so that they allocate in the stack, in all cases:

(declare dynamic-extent) will also optimize these specific calls:

9.7.8 Inlining foreign slot access

Given a structure definition:

(fli:define-c-struct foo-struct
  (a :int)
  (b :int))

you can inline access to a slot by declaring fli:foreign-slot-value inline and supplying the object-type:

(defun foo-a (struct)
  (declare (inline fli:foreign-slot-value))
  (fli:foreign-slot-value struct 'a :object-type 'foo-struct))

9.7.9 Built-in optimization of remove-duplicates and delete-duplicates

LispWorks optimizes cl:remove-duplicates and cl:delete-duplicates for lists when the test or test-not is one of a small set of known functions. These functions are currently cl:eq, cl:eql, cl:equal, cl:equalp, cl:=, cl:string=, cl:string-equal, cl:char= and cl:char-equal.


LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:19