11 speed & debug. lisp is really two languages: a language for writing fast programs a...
TRANSCRIPT
Speed - Introduction
Lisp is really two languages: A language for writing fast programs A language for writing programs fast
In the early stage, you can trade speed for convenience. Then, you can refine critical portions to make them faster once your program begins to crystallize
Speed - The Bottleneck Rule Optimization
Should be focused on bottlenecks Should not begin too early Should begin with algorithms
Programs tend to have a few bottlenecks that account for a great part of the execution time
“Most of the running time in none-IO-bound programs is concentrated in about 3% of the source text. Optimizing these parts of the program will make it run noticeably faster; optimizing the rest of the program will be a waste of time on comparison.”, by Knuth
Speed - The Bottleneck Rule
Optimize the program from the top Make sure that you’re using the most efficient
algorithm before you resort to low-level coding tricks
Decisions about algorithms have to be made early
Speed - Compilation
Five parameters control the way your code is compiled Speed: the speed of the code produced by the compiler Compilation-speed: the speed at which your program
will be compiled Safety: the amount of error-checking done in the
object code Space: the size and memory needs of the object code Debug: the amount of information retained for
debugging
Speed - Compilation
The compilation parameters are not real variables. They are assigned weights from 0 (unimportant) to 3 (most important) in declarations (defun bottlenect (…)
(do (…) (…) (do (…) (…) (declare (optimize (speed 3) (safety 0))) …)))
Add such declarations until the code was finished and tested
Speed - Compilation
Inline function Without the cost of calling functions Similar to macros Macro▪ Since macros use mere textual substitution, this may result in
unintended side-effects and inefficiency due to re-evaluation of arguments and order of operations
▪ Compiler errors within macros are often difficult to understand, because they refer to the expanded code
Recursive functions cannot be inlined If an inlined function is redefined, we have to recompile any
function that calls it
Speed - Compilation
(declaim (inline single?))(defun single? (lst) (and (consp lst) (null (cdr lst))))
(defun foo (x) (single? (bar x)))
When foo is compiled(defun foo (x) (let ((lst (bar x))) (and (consp lst) (null (cdr lst)))))
Speed - Type Declarations In most languages, you have to declare the type of each
variable, and the variable can only hold values of that type → strongly typed language
Common List uses manifest typing (run-time typing) Type information is attached to the data objects Type information is used at run-time Variables can hold objects of any type We have to pay for this flexibility in speed▪ The function have to look at the types of each of its arguments at run-
time If we just want one type, say, fixnum, this is an inefficient way
Type Declarations
In Common Lisp, type declarations are completely optional
Global declarations are made with declaim (declaim (type fixnum *count*))
;type can be omitted Local declarations are made with declare
(defun poly (a b x) (declare (fixnum a b x)) (+ (* a (expt x 2)) (* b x)))
Type Declarations
Type declarations are particularly important for the contents of complex objects, including arrays and structures Declarations can improve efficiency
The compiler can determine the types of arguments to functions and represent these objects more efficients▪ If nothing is known about the type of elements an array will
contain, it has to be represented in memory as a block of pointers
▪ If it is known that the array will only contain, say, double-floats, then the array can be represented as a block of actual double-floats
Type Declarations
(setf x (vector 1.234d0 2.345d0 3.456d0) y (make-array 3 :element-type ‘double-float) (aref y 0) 1.234d0 (aref y 1) 2.345d0 (aref y 2) 3.456d0)
Speed - Type Declarations
(setf a (make-array ‘(1000 1000) :element-type ‘single-float :initial-element 1.0))(defun sim-elts (a) (declare (type (simple-array single-float (1000 1000)) a)) (let ((sum 0.0)) (declare (type single-float sum)) (dotimes (r 1000) (dotimes (c 1000) (incf sum (aref a r c)))) sum))
Speed - Type Declarations > (compile-file “..\\test\\declare.lsp”) > (load “..\\test\\declare.fas”) > (time (sum-elts a)) Real time: 0.823 sec. Run time: 0.8112052 sec. Space: 11999988 Bytes GC: 1, GC time: 0.1092007 sec. 1000000.0
> (compile-file “..\\test\\nodeclare.lsp”) > (load “..\\test\\nodeclare.fas”) > (time (sum-elts-nodeclare a))
Real time: 1.026 sec. Run time: 0.9984064 sec. Space: 11999988 Bytes GC: 2, GC time: 0.2340015 sec. 1000000.0
Speed - Garbage Avoidance
Dynamic allocation is slow Programs that cons a lot tend to run slowly in
Lisp implementations with bad garbage collectors
Until recently, most Lisp implementations have had bad garbage collectors
Efficient programs should cons as little as possible
Speed - Garbage Avoidance One of the easiest way is to use destructive functions
When you know it’s safe to modify a list, you can use delete instead of remove, nreverse instead of reverse, and so on
SAFE DESTRUCTIVE
append reverse remove remove-if remove-duplicates subst subst-if union intersection set-difference
nconc nreverse delete delete-if delete-duplicates nsubst nsubst-if nunion nintersection nset-difference .
Speed - Garbage Avoidance Use a pre-allocated vector instead of building it using conses
> (setf *print-array* t)T> (setf vec (make-array 10 :fill-pointer 2 :initial-element nil))#(NIL NIL)> (length vec)2> (vector-push ‘a vec)2> vec#(NIL NIL A)> (vector-pop vec)A> vec#(NIL NIL)
Speed – Fast Operators
svref is more efficient than aref
eq is more efficient than eql
(reduce #’+ ‘(1 2 3)) is more efficient than (apply #’+ ‘(1 2 3))
Debug
trace (defun count-atoms (expression)
(if (atom expression) 1 (+ (count-atoms (first expression)) (count-atoms (rest expresstion)))))
> (count-atoms ‘((this is) (a test)))7 ;we expect 4
Debug
> (trace count-atoms);; Tracing function COUNT-ATOMS.
(COUNT-ATOMS)
(count-atoms '((this is) (a test)))
Debug
2. Trace: (COUNT-ATOMS '((THIS IS) (A TEST)))
3. Trace: (COUNT-ATOMS '(THIS IS))
4. Trace: (COUNT-ATOMS 'THIS)
4. Trace: COUNT-ATOMS ==> 1
4. Trace: (COUNT-ATOMS '(IS))
5. Trace: (COUNT-ATOMS 'IS)
5. Trace: COUNT-ATOMS ==> 1
5. Trace: (COUNT-ATOMS 'NIL)
5. Trace: COUNT-ATOMS ==> 1
4. Trace: COUNT-ATOMS ==> 2
3. Trace: COUNT-ATOMS ==> 3
3. Trace: (COUNT-ATOMS '((A TEST)))
4. Trace: (COUNT-ATOMS '(A TEST))
5. Trace: (COUNT-ATOMS 'A)
5. Trace: COUNT-ATOMS ==> 1
5. Trace: (COUNT-ATOMS '(TEST))
6. Trace: (COUNT-ATOMS 'TEST)
6. Trace: COUNT-ATOMS ==> 1
6. Trace: (COUNT-ATOMS 'NIL)
6. Trace: COUNT-ATOMS ==> 1
5. Trace: COUNT-ATOMS ==> 2
4. Trace: COUNT-ATOMS ==> 3
4. Trace: (COUNT-ATOMS 'NIL)
4. Trace: COUNT-ATOMS ==> 1
3. Trace: COUNT-ATOMS ==> 4
2. Trace: COUNT-ATOMS ==> 7
7
Debug
(defun count-atoms (expression) (cond ((atom expression) 1) ((null expression) 0) (t (+ (count-atoms (first expression)) (count-atoms (rest expression))))))
> (count-atoms ‘((this is) (a test)))7
Debug
(defun count-atoms (expression) (cond ((null expression) 0) ((atom expression) 1) (t (+ (count-atoms (first expression)) (count-atoms (rest expression))))))
> (count-atoms ‘((this is) (a test)))4
> (untrace count-atoms)