Thursday, April 10, 2008

Evil eval

Common Lisp, in common with many other languages, has a command called 'eval', which evaluates the input it is given as a Lisp program. This seems like a nice idea, but there are issues with it.

SBCL, the implementation I generally use, compiles everything it's given, by default. This means that every time it is told to eval something, it must compile it. There is an interpreter, but it's not used at all by default, and is horribly slow. The need to compile any form given to eval is a problem even for very simple things, though:

This is SBCL 1.0.12, an implementation of ANSI Common Lisp.
More information about SBCL is available at .

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (time (dotimes (i 10000000) (+ 4 5)))

Evaluation took:
0.014 seconds of real time
0.01 seconds of user run time
0.0 seconds of system run time
0 calls to %EVAL
0 page faults and
0 bytes consed.
NIL
* (time (dotimes (i 10000000) (eval '(+ 4 5))))

Evaluation took:
10.175 seconds of real time
9.77 seconds of user run time
0.37 seconds of system run time
[Run times include 0.27 seconds GC run time.]
0 calls to %EVAL
0 page faults and
2,720,007,360 bytes consed.

Yep, that's 2.7 gigabytes.

By contrast, clisp interprets everything it's given by default, and its compiler produces far slower code than SBCL's. Here is clisp doing the same thing (note the ASCII art menorah, which has caused endless squabbling on the clisp mailing list...):

i i i i i i i ooooo o ooooooo ooooo ooooo
I I I I I I I 8 8 8 8 8 o 8 8
I \ `+' / I 8 8 8 8 8 8
\ `-+-' / 8 8 8 ooooo 8oooo
`-__|__-' 8 8 8 8 8
| 8 o 8 8 o 8 8
------+------ ooooo 8oooooo ooo8ooo ooooo 8

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2006

[1]> (time (dotimes (i 10000000) (+ 4 5)))
Real time: 8.722473 sec.
Run time: 8.71 sec.
Space: 752 Bytes
NIL
[2]> (time (dotimes (i 10000000) (eval '(+ 4 5))))
Real time: 11.29976 sec.
Run time: 11.27 sec.
Space: 752 Bytes


And timing corresponding compiled functions:

; no eval
(time (bla))
Real time: 0.455828 sec.
Run time: 0.45 sec.
Space: 0 Bytes

; eval
(time (bla))
Real time: 2.433657 sec.
Run time: 2.44 sec.
Space: 0 Bytes

Lesson (hopefully) learned: while 'eval' could seem like a reasonable option on clisp and other Lisps with a good built-in interpreter, causing only a small slow-down, you do not want to use it in speed-critical, much-visited areas of code. After all, someone may want to run your code with SBCL, or another implementation without a decent interpreter. Obviously, it's fine for things which hardly ever happen.

No comments:

Post a Comment