Path: utzoo!attcan!uunet!wuarchive!zaphod.mps.ohio-state.edu!uakari.primate.wisc.edu!uflorida!ufqtp!beck From: beck@qtp.ufl.edu (Sullivan Beck) Newsgroups: comp.emacs Subject: Byte-compile summary. Message-ID: <1162@red15.qtp.ufl.edu> Date: 9 Oct 90 16:26:38 GMT Reply-To: beck@qtp.ufl.edu (Sullivan Beck) Organization: University of Florida Quantum Theory Project Lines: 105 > Second question. I am writing a lisp program which does a lot of > text operations and uses a lot of alists, lists, and vectors. It > does so much in fact that it is a little on the slow side and I will > eventually want to byte-compile it in order to speed it up. What > can I do now to help get the best speed once I byte-compile it? First of all, some clarification. The reason I asked this question in the first place is because of a statement by David Gillespie in his calc program. From the file calc-INSTALL: | Calc is written in a way that maximizes performance when its code has | been byte-compiled; a side effect is that performance is seriously | degraded if it *isn't* compiled. Thus, it is essential to compile the | Calculator before trying to use it. The Emacs command `M-x Some general suggestions were made by Raul about lisp programming. | 1) C-coded Lisp functions will be faster than those written in | Lisp. | 2) Don't use functions that set the mark, or display a message | in the minibuffer, in internal Lisp code. Typically these are | user-oriented, and could be rewritten to be slightly faster | to work in Lisp code. | 3) When calling Lisp functions that are not C-coded (i.e., | (subrp 'function) is `nil'), it is a good idea to ALWAYS | check the Lisp source code to see what they do, and see if | you really need the overhead. For example: the difference | between "next-line" and "forward-line". "next-line" does some | things that you normally don't need to worry about when | using it internally. | 4) `substitute-command-keys' warning: it takes a while, up to | a full second, to execute. Try it! And from David Gillespie: | There are two major reasons for this statement: | | 1. Calc uses lots of Lisp macros nested inside the deepest loops. In | compiled code these macros are expanded once at compile-time, but in | interpreted code each macro is expanded each time it is called. | | 2. Calc's inner loops try to use only Lisp functions which are handled | directly by the byte-compiler. When you write "(stringp x)", the | byte-compiler translates this into one byte-code to fetch "x" and | one byte-code to execute "stringp". But when you write "(integerp x)", | the byte-compiler doesn't have a byte-code for "integerp" so it | produces a general function-call which is only slightly faster than | an interpreted call would be. | | A funny thing about Emacs Lisp is that function calls are about the most | expensive thing in the language. Since Lisp is "supposed" to be written | recursively this seems rather odd; in fact, Calc was originally written | using recursion instead of loops in most places but once I read the | source to the Lisp interpreter I realized "while" loops would be much | faster. In fact, rewriting a few inner loops to use "while" made the | whole calculator twice as fast! | | There are a number of reasons for this; for example, at any time a program | may call "(debug)" to run the Lisp Debugger and display a backtrace, so | all function calls must keep complete backtrace information just in case. | Each function call must also check to see if the garbage collector needs | to be invoked, check for and handle &optional and &rest arguments, and so | on. | | The list of byte-codes can be found at the top of "bytecomp.el" in the | Emacs source directory. Read this list, memorize it, paste it on the wall | or preferably on the inside of your forehead. This list is your friend | if you are trying to get performance out of Emacs Lisp. Also, it can't | hurt to run "disassemble" on your most critical functions to see how | well they compile. | | Another example: The "elt" function can do both "nth" (if its argument | is a list) or "aref" (if its argument is a vector). I used it all over | the place because I figured there wouldn't be much difference and I liked | it a bit more. But it turns out that "elt" is not byte-compiled whereas | "nth" and "aref" are. I went back through my code, and bingo, another | factor of two in performance! | | Other fun tricks: "(+ a b c)" is not byte-compiled, but "(+ a (+ b c))" is. | "*" is not byte-compiled at all, so "(+ a (+ a a))" is faster than "(* 3 a)". | The "setq" function returns the last value that was assigned; if you call it | in a context that does not use the value the compiler must insert a "discard" | instruction to throw away the value, which was left there by a superfluous | "dup" instruction. So "(setq a1 b1 a2 b2)" is faster than "(setq a1 b1) | (setq a2 b2)", and "(if (setq a b) ...)" is faster than "(setq a b) | (if a ...)". All of these are minor gains, but minors gains inside a | tight loop can be important. | | The function-call overhead is enough that it's especially important to use | high-level structural functions whenever you can. For example, if you have | a data structure that you will be searching often, see if you can arrange | so that it can be searched by "memq", "assq", or "assoc". This will be | much faster than an equivalent "while" loop. "memq" is even byte-compiled | (although a single function-call overhead is probably worth having your | whole loop execute in C anyway). Note that "equal" is not byte-compiled, | so using "assoc" to search a list of "equal" things is an especially big | win. For example, I use "(assoc s '(("one") ("two") ("three")))" to check | if "s" is one of these three strings. It seems like overkill, but it's | better because all three tests are done in C. Hope this helps any lisp programmers out there as much as it helped me. Thanks to everyone who responed. S. Beck beck@qtp.ufl.edu