Code Poet and Ale aficionado
69 stories
·
2 followers

Team Chat

8 Comments and 28 Shares
2078: He announces that he's finally making the jump from screen+irssi to tmux+weechat.
Read the whole story
Share this story
Delete
7 public comments
stsquad
256 days ago
reply
Truth
Cambridge, UK
hooges
260 days ago
reply
IRC, never die!
Topeka, KS
encolpe
260 days ago
reply
Si true
Lyon, France
emdeesee
260 days ago
reply
OK, fine. When the galactic singularity becomes an option, I'll consider switching from IRC.
Lincoln, NE
beowuff
260 days ago
Don't worry. It'll still run irc as it's back end, so you can still connect.
Fidtz
260 days ago
reply
2051, solid 1960's style future prediction there!
Covarr
260 days ago
reply
What if I'm only still on IRC because the rest of my groups are?
Moses Lake, WA
alt_text_bot
260 days ago
reply
2078: He announces that he's finally making the jump from screen+irssi to tmux+weechat.

Chris Wellons: Some Performance Advantages of Lexical Scope

1 Share

I recently had a discussion with Xah Lee about lexical scope in Emacs Lisp. The topic was why lexical-binding exists at a file-level when there was already lexical-let (from cl-lib), prompted by my previous article on JIT byte-code compilation. The specific context is Emacs Lisp, but these concepts apply to language design in general.

Until Emacs 24.1 (June 2012), Elisp only had dynamically scoped variables — a feature, mostly by accident, common to old lisp dialects. While dynamic scope has some selective uses, it’s widely regarded as a mistake for local variables, and virtually no other languages have adopted it.

Way back in 1993, Dave Gillespie’s deviously clever lexical-let macro was committed to the cl package, providing a rudimentary form of opt-in lexical scope. The macro walks its body replacing local variable names with guaranteed-unique gensym names: the exact same technique used in macros to create “hygienic” bindings that aren’t visible to the macro body. It essentially “fakes” lexical scope within Elisp’s dynamic scope by preventing variable name collisions.

For example, here’s one of the consequences of dynamic scope.

(defun inner ()
  (setq v :inner))

(defun outer ()
  (let ((v :outer))
    (inner)
    v))

(outer)
;; => :inner

The “local” variable v in outer is visible to its callee, inner, which can access and manipulate it. The meaning of the free variable v in inner depends entirely on the run-time call stack. It might be a global variable, or it might be a local variable for a caller, direct or indirect.

Using lexical-let deconflicts these names, giving the effect of lexical scope.

(defvar v)

(defun lexical-outer ()
  (lexical-let ((v :outer))
    (inner)
    v))

(lexical-outer)
;; => :outer

But there’s more to lexical scope than this. Closures only make sense in the context of lexical scope, and the most useful feature of lexical-let is that lambda expressions evaluate to closures. The macro implements this using a technique called closure conversion. Additional parameters are added to the original lambda function, one for each lexical variable (and not just each closed-over variable), and the whole thing is wrapped in another lambda function that invokes the original lambda function with the additional parameters filled with the closed-over variables — yes, the variables (e.g. symbols) themselves, not just their values, (e.g. pass-by-reference). The last point means different closures can properly close over the same variables, and they can bind new values.

To roughly illustrate how this works, the first lambda expression below, which closes over the lexical variables x and y, would be converted into the latter by lexical-let. The #: is Elisp’s syntax for uninterned variables. So #:x is a symbol x, but not the symbol x (see print-gensym).

;; Before conversion:
(lambda ()
  (+ x y))

;; After conversion:
(lambda (&rest args)
  (apply (lambda (x y)
           (+ (symbol-value x)
              (symbol-value y)))
         '#:x '#:y args))

I’ve said on multiple occasions that lexical-binding: t has significant advantages, both in performance and static analysis, and so it should be used for all future Elisp code. The only reason it’s not the default is because it breaks some old (badly written) code. However, lexical-let doesn’t realize any of these advantages! In fact, it has worse performance than straightforward dynamic scope with let.

  1. New symbol objects are allocated and initialized (make-symbol) on each run-time evaluation, one per lexical variable.

  2. Since it’s just faking it, lexical-let still uses dynamic bindings, which are more expensive than lexical bindings. It varies depending on the C compiler that built Emacs, but dynamic variable accesses (opcode varref) take around 30% longer than lexical variable accesses (opcode stack-ref). Assignment is far worse, where dynamic variable assignment (varset) takes 650% longer than lexical variable assignment (stack-set). How I measured all this is a topic for another article.

  3. The “lexical” variables are accessed using symbol-value, a full function call, so they’re even slower than normal dynamic variables.

  4. Because converted lambda expressions are constructed dynamically at run-time within the body of lexical-let, the resulting closure is only partially byte-compiled even if the code as a whole has been byte-compiled. In contrast, lexical-binding: t closures are fully compiled. How this works is worth its own article.

  5. Converted lambda expressions include the additional internal function invocation, making them slower.

While lexical-let is clever, and occasionally useful prior to Emacs 24, it may come at a hefty performance cost if evaluated frequently. There’s no reason to use it anymore.

Constraints on code generation

Another reason to be weary of dynamic scope is that it puts needless constraints on the compiler, preventing a number of important optimization opportunities. For example, consider the following function, bar:

(defun bar ()
  (let ((x 1)
        (y 2))
    (foo)
    (+ x y)))

Byte-compile this function under dynamic scope (lexical-binding: nil) and disassemble it to see what it looks like.

(byte-compile #'bar)
(disassemble #'bar)

That pops up a buffer with the disassembly listing:

0       constant  1
1       constant  2
2       varbind   y
3       varbind   x
4       constant  foo
5       call      0
6       discard
7       varref    x
8       varref    y
9       plus
10      unbind    2
11      return

It’s 12 instructions, 5 of which deal with dynamic bindings. The byte-compiler doesn’t always produce optimal byte-code, but this just so happens to be nearly optimal byte-code. The discard (a very fast instruction) isn’t necessary, but otherwise no more compiler smarts can improve on this. Since the variables x and y are visible to foo, they must be bound before the call and loaded after the call. While generally this function will return 3, the compiler cannot assume so since it ultimately depends on the behavior foo. Its hands are tied.

Compare this to the lexical scope version (lexical-binding: t):

0       constant  1
1       constant  2
2       constant  foo
3       call      0
4       discard
5       stack-ref 1
6       stack-ref 1
7       plus
8       return

It’s only 8 instructions, none of which are expensive dynamic variable instructions. And this isn’t even close to the optimal byte-code. In fact, as of Emacs 25.1 the byte-compiler often doesn’t produce the optimal byte-code for lexical scope code and still needs some work. Despite not firing on all cylinders, lexical scope still manages to beat dynamic scope in performance benchmarks.

Here’s the optimal byte-code, should the byte-compiler become smarter someday:

0       constant  foo
1       call      0
2       constant  3
3       return

It’s down to 4 instructions due to computing the math operation at compile time. Emacs’ byte-compiler only has rudimentary constant folding, so it doesn’t notice that x and y are constants and misses this optimization. I speculate this is due to its roots compiling under dynamic scope. Since x and y are no longer exposed to foo, the compiler has the opportunity to optimize them out of existence. I haven’t measured it, but I would expect this to be significantly faster than the dynamic scope version of this function.

Optional dynamic scope

You might be thinking, “What if I really do want x and y to be dynamically bound for foo?” This is often useful. Many of Emacs’ own functions are designed to have certain variables dynamically bound around them. For example, the print family of functions use the global variable standard-output to determine where to send output by default.

(let ((standard-output (current-buffer)))
  (princ "value = ")
  (prin1 value))

Have no fear: With lexical-binding: t you can have your cake and eat it too. Variables declared with defvar, defconst, or defvaralias are marked as “special” with an internal bit flag (declared_special in C). When the compiler detects one of these variables (special-variable-p), it uses a classical dynamic binding.

Declaring both x and y as special restores the original semantics, reverting bar back to its old byte-code definition (next time it’s compiled, that is). But it would be poor form to mark x or y as special: You’d de-optimize all code (compiled after the declaration) anywhere in Emacs that uses these names. As a package author, only do this with the namespace-prefixed variables that belong to you.

The only way to unmark a special variable is with the undocumented function internal-make-var-non-special. I expected makunbound to do this, but as of Emacs 25.1 it does not. This could possibly be considered a bug.

Accidental closures

I’ve said there are are absolutely no advantages to lexical-binding: nil. It’s only the default for the sake of backwards-compatibility. However, there is one case where lexical-binding: t introduces a subtle issue that would otherwise not exist. Take this code for example (and nevermind prin1-to-string for a moment):

;; -*- lexical-binding: t; -*-

(defun function-as-string ()
  (with-temp-buffer
    (prin1 (lambda () :example) (current-buffer))
    (buffer-string)))

This creates and serializes a closure, which is one of Elisp’s unique features. It doesn’t close over any variables, so it should be pretty simple. However, this function will only work correctly under lexical-binding: t when byte-compiled.

(function-as-string)
;; => "(closure ((temp-buffer . #<buffer  *temp*>) t) nil :example)"

The interpreter doesn’t analyze the closure, so just closes over everything. This includes the hidden variable temp-buffer created by the with-temp-buffer macro, resulting in an abstraction leak. Buffers aren’t readable, so this will signal an error if an attempt is made to read this function back into an s-expression. The byte-compiler fixes this by noticing temp-buffer isn’t actually closed over and so doesn’t include it in the closure, making it work correctly.

Under lexical-binding: nil it works correctly either way:

(function-as-string)
;; -> "(lambda nil :example)"

This may seem contrived — it’s certainly unlikely — but it has come up in practice. Still, it’s no reason to avoid lexical-binding: t.

Use lexical scope in all new code

As I’ve said again and again, always use lexical-binding: t. Use dynamic variables judiciously. And lexical-let is no replacement. It has virtually none of the benefits, performs worse, and it only applies to let, not any of the other places bindings are created: function parameters, dotimes, dolist, and condition-case.

Read the whole story
Share this story
Delete

Work

12 Comments and 28 Shares
Despite it being imaginary, I already have SUCH a strong opinion on the cord-switch firing incident.
Read the whole story
Share this story
Delete
12 public comments
wreichard
354 days ago
reply
This goes along with one of what I think of life's best hidden lessons: if you assume that things around you are the way they are for some reason, you learn amazing things.
Earth
brico
354 days ago
reply
A curiously bureaucratic and teleological vision from Randall; I look at these and see evolution, not design. The gooseneck lamp goes back well over a century. And the glass tumbler and the wooden table with aprons are even older forms.
Brooklyn, NY
Brstrk
355 days ago
reply
No mention on any documentation processes. Some heroes just go unsung.
duerig
355 days ago
reply
He missed designing for manufacturability. "This shape was chosen because it is easy to stamp out of sheet metal." or "These two parts snap together in order to eliminate two screws."
duerig
355 days ago
Also, I found out how they make plastic soda bottles recently. First, a test-tube shaped piece of plastic is cast. Then before it has cooled, they stick it at the opening of a mold and blow air through it until the sides conform to the shape of the mold. For large bottles (2-liter), they have a rod that automatically pushes the bottom of the tube down deeper into the mold as air is blown in. It is like automated glassblowing with plastic, done countless times over and over for a disposable bottle.
infini
355 days ago
reply
supernormal design as jasper morrison and naoko fukusawa would call it
Asia, EU, Africa
dukeofwulf
355 days ago
reply
Related: "I, Pencil." http://www.econlib.org/library/Essays/rdPncl1.html
Covarr
355 days ago
reply
An artist spent several minutes deciding what objects to put on this table.
Moses Lake, WA
ossiander
355 days ago
reply
I could see this.
magicseth
355 days ago
reply
On point
mburch42
355 days ago
reply
My life.
reconbot
355 days ago
reply
Truth behind objects
New York City
alt_text_bot
355 days ago
reply
Despite it being imaginary, I already have SUCH a strong opinion on the cord-switch firing incident.

New zine: Linux debugging tools you'll love

3 Shares

HELLO FRIENDS. I am announcing this everywhere because I'm very excited about it. I released a new zine today! Read it here! Read all my zine things at jvns.ca/zines!

This zine is about some of my favorite Linux debugging tools, especially tools that I don't think are as well-known as they should be. It covers strace, opensnoop/eBPF, and dstat! netcat, netstat, tcpdump, wireshark, and ngrep! And there's a whole section on perf because perf is the best.

If you don't know what any of those tools I just mentioned are -- PERFECT. You are who this zine is for!!! Read it and find out why I love them! Also, a lot of these tools happen to work on OS X :)

I've been really delighted to see that a ton of people have enjoyed & learned something new from this zine, whether they just started using Linux (!!!) or have been debugging on Linux for 10 years.

As usual, there are 3 versions. If you print it, you can print as many as you want! Give them to your friends! Teach them about tcpdump!

The cover art is by Monica, who is the best.

Thanks to my amazing partner Kamal Marhubi for endless reviews, and many many other people. It turns out that a zine project takes a long time! I do not even know how people write books.

Read the whole story
Share this story
Delete

Know Your Eels

3 Shares

Know Your Eels

The wonders of biology.

Read the whole story
Share this story
Delete

Marcin Borkowski: Emacs Lisp closures demystified

1 Comment
It is often claimed that one of the advantages of closures (as in “lexical scoping”) is information hiding (a.k.a. encapsulation). This is true, but as this post shows, you can’t really hide anything in Emacs Lisp;-).
Read the whole story
Share this story
Delete
1 public comment
stsquad
494 days ago
reply
It's in the details ;-)
Cambridge, UK
Next Page of Stories