michaelm@bcsaic.UUCP (michael maxwell) (06/23/86)
In article <3827@utah-cs.UUCP> shebs@utah-cs.UUCP (Stanley Shebs) writes: >In article <1311@well.UUCP> jjacobs@well.UUCP (Jeffrey Jacobs) writes: >[...] In fact, many expert Lisp hackers replace >functions with equivalent macros when possible, to save function call overhead >(I don't recommend this practice, most compilers have hooks to opencode user >functions when requested). My question doesn't really have anything to do with the original posting(s), rather I'm interested in this comment on macros vs. functions. Many of the functions I write are only called from one place in the program (not recursively!), and are never called by the user; they're just conveniences for top-down programming. I've often wondered whether turning them into macros wouldn't speed things up. Anyone care to comment on Stan's contention that turning such functions into macros doesn't speed things up? comment on the -- Mike Maxwell Boeing Artificial Intelligence Center ...uw-beaver!uw-june!bcsaic!michaelm
shebs@utah-cs.UUCP (Stanley Shebs) (06/30/86)
In article <574@bcsaic.UUCP> michaelm@bcsaic.UUCP (michael maxwell) writes: >In article <3827@utah-cs.UUCP> shebs@utah-cs.UUCP (Stanley Shebs) writes: >>In article <1311@well.UUCP> jjacobs@well.UUCP (Jeffrey Jacobs) writes: >>[...] In fact, many expert Lisp hackers replace >>functions with equivalent macros when possible, to save function call overhead >>(I don't recommend this practice, most compilers have hooks to opencode user >>functions when requested). > >My question doesn't really have anything to do with the original posting(s), >rather I'm interested in this comment on macros vs. functions. Many of the >functions I write are only called from one place in the program (not >recursively!), and are never called by the user; they're just conveniences >for top-down programming. I've often wondered whether turning them into macros >wouldn't speed things up. Anyone care to comment on Stan's contention that >turning such functions into macros doesn't speed things up? Well mostly yes and a little no. Consider the situation which I believe is an example of what you're talking about: (defun bar (x y) (foo x y 0)) (defun foo (a b c) (print c)) When you compile these, you get bar that consists of code to call foo, and foo that consists of code to call print. We can change foo to be defined as a macro: (defmacro foo (a b c) `(print ,c)) The first step of the compiler is macro-expansion. Macroexpansion replaces one piece of source code with another, so the compiler's work reduces to compiling (defun bar (x y) (print 0)) And when the compiler compiles this, bar will just be code to call print. We've saved a lot! One-half the number of function calls and some argument shuffling. The whole macro expansion business has gone away completely, and you don't even need to keep the definition of foo around at runtime (those among us who are still awake will see a problem). Now, before you run out to turn all your function calls into macros, there are a bunch of caveats. 1) This technique cannot be used if an argument to the macro is referenced twice and has a side effect. If foo is defined: (defun foo (a b c) (print c) (print c)) Then the obvious macro definition is (defmacro foo (a b c) `(progn (print ,c) (print ,c))) But if bar looks like (defun bar (x y) (foo x y (incf y))) then expansions yields (defun bar (x y) (progn (print (incf y)) (print (incf y)))) which will increment y twice instead of once! The solution where you lambda-bind the item that shows up twice, as in (defmacro foo (a b c) `(let ((tmp ,c)) (print tmp) (print tmp))) fixes the bug, but the let expands into a lambda application, which is (you guessed it) a function call! So you haven't won efficiency-wise. 2) As the first caveat showed by example, getting a macro semantically right can be tricky. Using macros to gain efficiency is asking for trouble, especially if the functions that you want to rewrite are complicated. 3) Use of macros can expand code size, since you are replacing a function call with the whole body. If this is done often, the increase may be noticeable. As a real fine point, note that increased code size might bolix the cache and virtual memory, slowing things down. 4) Macros in most Lisps (including CL) cannot be APPLYed. 5) The macro expansion process throws away the original body. In the above example, if I redefine foo, bar will be unaffected. If foo is a function call, bar will get the new function. 6) In the above example, the compiler is quite likely to compile these calls as jumps, since they are in tail-recursive position. It may or may not be the case that you get any advantage from the macro, or it may be a 1% improvement even if the foo function is called a lot. The only way to know for sure is to look at your compiled code and see what's being produced. Even then you have to have done all the other optimization things, like keeping track of conses and avoiding linear searches down long lists (member fn does it for instance). These sorts of things can totally swamp the cost of function calls. 7) Function protocols vary in expense. PSL's functions are cheap, Franz's are more expensive, Spice Lisp's are very expensive. There is little point to optimizing cheap function calls, unless you're doing realtime in Lisp or some such. 8) Some compiler (PCLS for instance) allow you to declare that functions will be coded "inline", which is almost identical to macroexpansion. The compiler can then do the tricky parts of figuring out the equivalent macro of a function and substitute, while you can still use it as a function etc. Common Lisp provides a standardized syntax for declaring this inlining operation, so it is becoming more prevalent as an option for Lispers. That's why I don't recommend using macros - it has to be a Lisp where you can't tell the compiler what to do, and you have to *know* that you will get a measurable speedup. >Mike Maxwell stan shebs