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:
:explain
declarations to make the compiler generate optimization hints, and: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.
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.
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.
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.
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)
The compiler optimizes tail calls, except in the following situations:
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:
hcl:special-global
declares that the symbol is never bound in the dynamic environment.
In SMP LispWorks the compiler signals an error if it detects that a symbol declared as hcl:special-global
will be bound in the dynamic environment, and at run time it also signals an error.
In non-SMP LispWorks the compiler gives an error, but there is no run time check. The run time behavior is the same as cl:special, with all accesses to the symbol in low safety.
hcl:special-global
is very useful, and because of the checks it is reasonably safe. It is useful not only for speed, but also to guard against unintentionally binding variables that should not be bound.
See also defglobal-parameter.
hcl:special-dynamic
declares that the symbol is always bound in the dynamic environment when it is accessed.
In high safety code, accessing the symbol when it is not bound in the dynamic environment signals an error. In low safety code it may result in unpredictable behavior.
In non-SMP LispWorks the only effect of this declaration is to make all access to the variable low safety.
hcl:special-dynamic
is useful, but because it can lead to unpredictable behavior you need to ensure that you test your program in high safety when you use it.
hcl:special-fast-access
declares that a symbol should be "fast access".
The semantics of the declaration is the same as cl:special, except that access to the variable is low safety. In addition, the compiler compiles access to the symbol in a way that speeds up the access, but also introduces a tiny reduction in the speed of the whole system. The balance between these effects is not obvious.
It is not obvious where hcl:special-fast-access
is useful. If you can ensure that the symbol is always bound or never bound then hcl:special-dynamic
or hcl:special-global
are certainly better.
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.
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.
(declare dynamic-extent)
will optimize these calls so that they allocate in the stack, in all cases:
(cons x y)
(list ...)
(list* ...)
(copy-list x)
(make-list x)
(vector ...)
(declare dynamic-extent)
will also optimize these specific calls:
(make-array n)
(make-array n :initial-element x)
without any other arguments.(make-foo ...)
where make-foo
is an inline structure constructor. The default constructor is declared inline automatically when none of the defstruct slot initforms are calls to functions.(make-string
n :element-type 'base-char)
(system:make-typed-aref-vector n)
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))
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