merits of Lisp vs Python
Alex Mizrahi
udodenko at users.sourceforge.net
Sat Dec 9 04:16:20 EST 2006
(message (Hello 'Kay)
(you :wrote :on '(8 Dec 2006 12:25:09 -0800))
(
KS> O.K. I agree with what you said about the generic function vs per
KS> object dictionary dispatch.
KS> But do the performance differences vanish when only builtin types and
KS> functions are used to express Python algorithms?
no.
language semantics require python to do dict lookup on each access to some
global function (or builtin) or variable.
i don't have enough time for in-depth analysis, but here's what's it.
suppose we have
def Fib(n):
if n < 2:
return 1
else:
return Fib(n -2) + Fib(n-1)
import dis
dis.dis(Fib)
you will see this:
...
21 LOAD_GLOBAL 1 (Fib)
24 LOAD_FAST 0 (n)
27 LOAD_CONST 2 (1)
30 BINARY_SUBTRACT
31 CALL_FUNCTION 1
34 LOAD_GLOBAL 1 (Fib)
37 LOAD_FAST 0 (n)
40 LOAD_CONST 1 (2)
43 BINARY_SUBTRACT
44 CALL_FUNCTION 1
47 BINARY_ADD
48 RETURN_VALUE
now let's check what is LOAD_GLOBAL in ceval.c (i have Python 2.4.1
sources):
case LOAD_GLOBAL:
w = GETITEM(names, oparg);
if (PyString_CheckExact(w)) {
/* Inline the PyDict_GetItem() calls.
WARNING: this is an extreme speed hack.
Do not try this at home. */
long hash = ((PyStringObject *)w)->ob_shash;
if (hash != -1) {
PyDictObject *d;
d = (PyDictObject *)(f->f_globals);
x = d->ma_lookup(d, w, hash)->me_value;
if (x != NULL) {
Py_INCREF(x);
PUSH(x);
continue;
}
d = (PyDictObject *)(f->f_builtins);
x = d->ma_lookup(d, w, hash)->me_value;
if (x != NULL) {
Py_INCREF(x);
PUSH(x);
continue;
}
goto load_global_error;
}
}
/* This is the un-inlined version of the code above */
x = PyDict_GetItem(f->f_globals, w);
if (x == NULL) {
x = PyDict_GetItem(f->f_builtins, w);
if (x == NULL) {
load_global_error:
format_exc_check_arg(
PyExc_NameError,
GLOBAL_NAME_ERROR_MSG, w);
break;
}
}
Py_INCREF(x);
PUSH(x);
continue;
so we can see PyDict access. moreover, it's inlined, since it's very
performance-critical function.
but even inlined PyDict access is not fast at all. ma_lookup is a long and
hairy function containing the loop.
moreover, we can see that there are two dict lookups -- into globals and
builins.
lookup into a global hash should about order of magnitude slower than simple
table fetch, so here's the root of python's slowness.
how lisp can be faster here? lisp has SYMBOLs and well-defined semantics of
source code parsing.
first source code is processed by reader, that outputs trees of code. each
variable or function name becomes a SYMBOL object. symbols are typically
interned into packages, but they don't need to be looked-up in the packages
in runtime -- in fact, it's not possible at all.
i can read a piece of code and then unintern some symbol from package --
that will not make that code invalid. packages are used mostly by reader.
(also, i can have many symbols with same name, if they are not interned --
and they will be all different objects)
in runtime, lisp has to lookup symbol's function -- but symbol can be
implemented as a structure, and symbol-function can be just it's field
access.
one more thing -- you can see lookup into builins, so they are in dict too!
and you can see that builtins dict is checked only if name is not found in
globals, so builtins are even slower than globals. that's a reason for
performance slowdowns too.
one can say that it's the only way to change builtins in runtime. yes, but
dict lookup is a big price for it (well, if python use symbols, it would be
faster).
in fact, Common Lisp also allows to redefine "built-in" function -- you can
define a symbol with a same name as builtin in some package and use it, it
will "shadow" symbol in the common-lisp package (common-lisp:+). you can
change symbol-function of this symbol in runtime and do whatever you want.
but symbols in common-lisp package are immutable. that makes it possible to
optimize code.
and there's inlining. for example, in fib definition:
(defun fib (n)
(if (< n 2)
1
(+ (fib (- n 2))
(fib (- n 1)))))
CLISP does not even use symbol FIB in function bytecodes -- it notes that
it's a recursive function calls, so instead of normal function call it does
a local jump.
)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
More information about the Python-list
mailing list