declaration is used to tell the compiler how to balance five qualities: the speed of the code generated; the amount of runtime error checking; the memory usage of the code, both in terms of code size and runtime memory usage; the amount of debugging information kept with the code; and the speed of the compilation process. An OPTIMIZE declaration consists of one or more lists, each containing one of the symbols SPEED, SAFETY, SPACE, DEBUG, and COMPILATION-SPEED, and a number from zero to three, inclusive. The number specifies the relative weighting the compiler should give to the corresponding quality, with 3 being the most important and 0 meaning not important at all. Thus, to make Common Lisp compile add more or less like a C compiler would, you can write it like this:

(defun add (x y)

(declare (optimize (speed 3) (safety 0)))

(declare (fixnum x y))

(the fixnum (+ x y)))

Of course, now the Lisp version suffers from many of the same liabilities as the C version—if the arguments passed aren't fixnums or if the addition overflows, the result will be mathematically incorrect or worse. Also, if someone calls add with a wrong number of arguments, it may not be pretty. Thus, you should use these kinds of declarations only after your program is working correctly. And you should add them only where profiling shows they'll make a difference. If you're getting reasonable performance without them, leave them out. But when profiling shows you a real hot spot in your code and you need to tune it up, go ahead. Because you can use declarations this way, it's rarely necessary to rewrite code in C just for performance reasons; FFIs are used to access existing C code, but declarations are used when C-like performance is needed. Of course, how close you can get the performance of a given piece of Common Lisp code to C and C++ depends mostly on how much like C you're willing to make it.

Another code-tuning tool built into Lisp is the function DISASSEMBLE. The exact behavior of this function is implementation dependent because it depends on how the implementation compiles code—whether to machine code, bytecodes, or some other form. But the basic idea is that it shows you the code generated by the compiler when it compiled a specific function.

Thus, you can use DISASSEMBLE to see whether your declarations are having any effect on the code generated. And if your Lisp implementation uses a native compiler and you know your platform's assembly language, you can get a pretty good sense of what's actually going on when you call one of your functions. For instance, you could use DISASSEMBLE to get a sense of the difference between the first version of add, with no declarations, and the final version. First, define and compile the original version.

(defun add (x y) (+ x y))

Then, at the REPL, call DISASSEMBLE with the name of the function. In Allegro, it shows the following assembly-language-like dump of the code generated by the compiler:

CL-USER> (disassemble 'add)

;; disassembly of #<Function ADD>

;; formals: X Y

;; code start: #x737496f4:

0: 55 pushl ebp

1: 8b ec movl ebp,esp

3: 56 pushl esi

4: 83 ec 24 subl esp,$36

7: 83 f9 02 cmpl ecx,$2

10: 74 02 jz 14

12: cd 61 int $97 ; SYS::TRAP-ARGERR

14: 80 7f cb 00 cmpb [edi-53],$0 ; SYS::C_INTERRUPT-PENDING

18: 74 02 jz 22

20: cd 64 int $100 ; SYS::TRAP-SIGNAL-HIT

22: 8b d8 movl ebx,eax

24: 0b da orl ebx,edx

26: f6 c3 03 testb bl,$3

29: 75 0e jnz 45

31: 8b d8 movl ebx,eax

33: 03 da addl ebx,edx

35: 70 08 jo 45

37: 8b c3 movl eax,ebx

39: f8 clc

40: c9 leave

41: 8b 75 fc movl esi,[ebp-4]

44: c3 ret

45: 8b 5f 8f movl ebx,[edi-113] ; EXCL::+_2OP

48: ff 57 27 call *[edi+39] ; SYS::TRAMP-TWO

51: eb f3 jmp 40

53: 90 nop

; No value

Clearly, there's a bunch of stuff going on here. If you're familiar with x86 assembly language, you can probably tell what. Now compile this version of add with all the declarations.

(defun add (x y)

(declare (optimize (speed 3) (safety 0)))

(declare (fixnum x y))

(the fixnum (+ x y)))

Now disassemble add again, and see if the declarations had any effect.

CL-USER> (disassemble 'add)

;; disassembly of #<Function ADD>

;; formals: X Y

;; code start: #x7374dc34:

0: 03 c2 addl eax,edx

2: f8 clc

3: 8b 75 fc movl esi,[ebp-4]

6: c3 ret

7: 90 nop

; No value

Looks like they did.

Delivering Applications

Another topic of practical importance, which I didn't talk about elsewhere in the book, is how to deliver software written in Lisp. The main reason I neglected this topic is because there are many different ways to do it, and which one is best for you depends on what kind of software you need to deliver to what kind of user with what Common Lisp implementation. In this section I'll give an overview of some of the different options.

If you've written code you want to share with fellow Lisp programmers, the most straightforward way to distribute it is as source code.[329] You can distribute a simple library as a

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату