TUGboat, Volume˜0˜(9999), No.˜0 1005
4.3.6 Performance
Let us tackle the problem of performance from a more
general point of view. One frequent yet misinformed
argument against dynamic languages is: “they are
slow”. From that point of view, it may seem odd
to even begin to envision the reimplementation of a
program such as T
E
X in a dynamic language.
One first and frequent misconception about in-
teractive languages is that as soon as they provide
a REPL, they must be interpreted. This is in fact
not the case. Nowadays, many Common Lisp im-
plementations such as SBCL don’t even provide an
interpreter. Instead, the REPL has a JIT (Just In
Time) compiler which compiles the expressions you
type and only then executes them. To put this in per-
spective, compare the processes of interpreting T
E
X
macros by expansion and executing Lisp functions
compiled to machine code. . .
Yet, starting with the assumption that perfor-
mance should indeed be a concern (this is not even
necessarily the case), the argument of slowness may
still make some sense in specific cases. For exam-
ple, it is obvious that performing type checking at
run-time instead of at compile-time will induce a
cost in execution speed. In general however, this
argument, as-is, is meaningless. For starters, let us
not confuse “dynamic language” with “dynamically
typed language”. A dynamic language gives you a
lot of run-time expressive power, but that doesn’t
necessarily mean that you have to use all of it, or
that it is impossible to optimize things away.
Let us continue on type checking in order to
illustrate this. Look again at the definition for our
“double” function:
(defun dbl (x) (* 2 x))
This function will no doubt be relatively slow, be-
cause Common Lisp has a lot of things to do at
run-time. For starters, it needs to check that
x
is
indeed a number. Next, the multiplication needs to
be polymorphic because you don’t double an integer
the same way you double a float or a complex. That
is in fact not the whole story, but we will stop here.
On the other hand, consider now the following
version:
(defun dbl (x)
(declare (optimize (speed 3) (safety 0))
(type fixnum x)
(the fixnum (* 2 x)))
In this function, we request that the compiler op-
timizes for speed instead of safety. The result is
that the compiler will “trust” us and bypass all dy-
namic checks. Next, we actually provide static type
information.
x
is declared to be a
fixnum
(roughly
equivalent to integers in other languages) and so is
the result of the multiplication. This is important
because there is no guarantee that the double of an
integer remains the same-size integer. Consequently,
in general, Lisp would need to allocate a bignum to
store the result.
As it turns out, compiling this new version of
the function leads to only 5 lines of machine code.
The compiler is even clever enough to avoid using
the integer multiplication operator, but a left shift
instruction instead. What we get in this context is
in fact the behavior of a statically and weakly typed
language such as C. Consequently, it should not be
surprising that the level of performance we get out
of this is comparable to that of equivalent C code.
Recent experimental studies have demonstrated that
this is indeed the case [19, 20].
This particular example is also a nice illustration
of what we meant earlier by saying that Common
Lisp is both a full-blown, industrial scale, general
purpose programming language, and a scripting lan-
guage at the same time. When working at the script-
ing level, the first version of
dbl
is quick and good
enough. When working in the core of your applica-
tion however, you appreciate it when the language
provides a lot of tools (optimization ones notably)
to adjust your code to your specific needs.
4.4 Extensibility and customizability
Another aspect of the language well worth its own sec-
tion is its level of extensibility (adding new behavior)
and customizability (modifying existing behavior).
We know how important this is in the (L
A
)T
E
X world,
which is a complicated ball of intermixed threads all
interacting with each other [
21
], which wouldn’t be
possible without the level of intercession that T
E
X
macros offer. Similarly, at least part of the success
of LuaT
E
X is due to its ability to provide access to
T
E
X’s internals, so it seems that there is also a lot
of interest in this area.
4.4.1 Homoiconicity and reflection
Once again, Common Lisp is here to help. We men-
tioned earlier how the root of extensibility and cus-
tomizability in Common Lisp is its highly reflexive
nature. Reflection is usually decoupled into introspec-
tion (the ability to examine yourself) and intercession
(the ability to modify yourself).
In Lisp, reflection is supported in the most direct
and simple way one could think of. Remember the
expression
(+ 1 2)
, with or without evaluation? As
we said before, this expression can either represent
a call to the function “sum” with the arguments 1
and˜2, or the list of three elements: the symbol
+
and
Star T
E
X: The Next Generation