25
Functional Programming 11 Speed & Debug

11 Speed & Debug. Lisp is really two languages: A language for writing fast programs A language for writing programs fast In the early stage,

Embed Size (px)

Citation preview

Functional Programming

11 Speed & Debug

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)

Debug

> (dribble “record.txt”) ;記錄在 toplevel的過程……> (dribble) ;stop dribbling

> (ed “record.txt”)

Final Projects

Write a Lisp program with at least 15 functions Due June 30 Report Source code Demo