author | Alan Dipert
<alan@dipert.org> 2019-12-12 20:34:26 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-12-12 20:34:26 UTC |
parent | b42243321df949c15f249705987e2f5d76026669 |
posts/collecting-macro-edition.md | +55 | -5 |
diff --git a/posts/collecting-macro-edition.md b/posts/collecting-macro-edition.md index 313822e..bc3eb28 100644 --- a/posts/collecting-macro-edition.md +++ b/posts/collecting-macro-edition.md @@ -1,10 +1,10 @@ --- date: "2019-12-12" keywords: ["LOOP", "collect", "lists", "macros"] -title: "Collecting without LOOP: Funcall Begone" +title: "Collecting without LOOP: Macro Edition" id: "urn:uuid:a2d4ab9a-1c99-11ea-a6e0-843a4b7c6000" abstract: | - Implementing COLLECTING with a macro interface instead of a functional interface. + Implementing COLLECTING with a macro interface instead of a function. --- In a [previous post](collecting.html), I professed my fondness for the @@ -15,18 +15,68 @@ delivered as a function called `COLLECTOR`. `COLLECTOR` compared to `collect`: function call overhead. In this post, we'll survey a few tools Common Lisp provides that can -be used to eliminate function call overhead, and end up with a new, -[macro][macro]-based collecting contraption: `WITH-COLLECT`. +be used to eliminate function call overhead, and use them to concoct a +new, [macro][macro]-based collecting contraption: `WITH-COLLECT`. ## Wait, function calls are slow? -Well, no. But there is some overhead associated with a function +Well, no, but there is some overhead associated with a function call. Usually, at a minimum: 1. The function reference must be resolved 1. A stack frame must be allocated 1. Arguments must be copied to the stack +One way that a compiler can optimize away the overhead of a function +call is by _inlining_ the function. That's the process of replacing +the place where a function is called, the [call site][call-site], with +the implementation of the function. + +Of course, functions are such a powerful tool that in most cases, +their overhead is well worth it. We're interested in inlining here +only because as library authors we want the collecting operation to be +as fast as possible, and because the use-case we have in mind — +collecting values in a single lexical block of code — doesn't +strictly require an actual function to exist. + +Honestly, I'm not really used to thinking too much about function call +overhead.[^r] Much of my professional programming experience has been +with the JVM and JavaScript, two platforms with optimizing [tracing +JIT][jit] compilers that do all kinds of optimization for you, +including automatically inlining. As programs on these platforms run, +they are continually analyzed and recompiled in order to run faster. + + +## Inlining and CL + +Common Lisp was specified before tracing JITs were a thing, but it +does have pervasive facilities for all kinds of _static_ optimization +strategies, including inlining. Mechanisms supporting inlining +directly or indirectly that I'm aware of include: + +* Macros ([`DEFMACRO`][defmacro], [`MACROLET`][macrolet]) +* Compiler macros ([`DEFINE-COMPILER-MACRO`][cmacro]) +* The [`INLINE`][inline] [declaration][declare] + +Here's an example showing the functional way, `COLLECTOR`, compared to +what I have in mind with `WITH-COLLECT`. Both expressions evaluate to +`(2 4 6 8 10)`: + +~~~{.lisp} +;; with COLLECTOR from previous post +(let ((collect (collector))) + (dotimes (i 10 (funcall collect)) + (let ((i (+1 i))) + (when (evenp i) + (funcall collect i))))) + +;; with WITH-COLLECT, which we haven't implemented yet +(with-collect collect + (dotimes (i 10) + (let ((i (1+ i))) + (when (evenp i) + (collect i))))) +~~~ In this post, we'll address the function call overhead question with a new, [macro][macro]-based collecting contraption, `WITH-COLLECT`. But