3

While investigating closures in javascript I came up with the little example below and I don't really understand what's going on.

I was hoping to play with the garbage collector assuming that declaring variables with var inside a function in a tight loop would cause tons of allocations an deallocations. I was trying to avoid this by putting my var declarations in the parent scope of a closure and was expecting that the closured function would be faster. However bad this idea might be in the first place I stumbled upon this litte problem.

var withClosure = function() { var a, b, c, d, e, f, g; return function () { a = 1; b = 2; c = 3; d = 4; e = 5; f = 6; g = 7; }; }(); var withoutClosure = function () { var a = 1; var b = 2; var c = 3; var d = 4; var e = 5; var f = 6; var g = 7; }; console.time("without"); for (var i = 0; i < 1000000000; i++) { withoutClosure(); } console.timeEnd("without"); console.time("withcsr"); for (var i = 0; i < 1000000000; i++) { withClosure(); } console.timeEnd("withcsr"); /* Output on my machine: without: 1098.329ms withcsr: 8878.812ms Tested with node v.6.0.0 and Chrome 50.0.2661.102 (64-bit) */ 

The fact that I assign to the variables in the parent scope makes the closure run 8 times slower than the normal version on my machine. Using more variables makes it worse. If I just read the variables instead of assigning to them the problem isn't there.

What causes this? Can someone explain?

7
  • 1
    Maybe the JS engine is optimising the functions differently - the non closure one could be optimised to an empty function given none of the variables are read. Commented Jun 21, 2016 at 11:21
  • returning the sum of the variables at the end of each function (thus reading the values) doesn't change it. Commented Jun 21, 2016 at 11:24
  • Closures inside a loop need to reference i outside of loop. The variables of inner function are the value when inner function was called, I don't think it follow each iteration as you expect it to. Commented Jun 21, 2016 at 11:30
  • Could you please rephrase your point. I can't make sense of the comment. Commented Jun 21, 2016 at 21:31
  • if it is assembly, may be more make sense, you can use many registers that cpu provide for particular function, if you use outside like another function, push, pop stack variables might make program bound to memory speed. Commented Jun 26, 2016 at 13:49

2 Answers 2

3
+50

In the example without the closure any decent Javascript engine will realise that the variables within the function are initialised but never read before going out of scope and thus can be removed without affecting the output of the function.

In the example with the closure the variables remain in scope and thus can't be optimised out.

This talk explains in depth some of the optimisations JIT Javascript compilers make: https://www.youtube.com/watch?v=65-RbBwZQdU

Sign up to request clarification or add additional context in comments.

5 Comments

This was already suggested in a comment. I also tried another example where I read, sum and return the values. jsfiddle.net/r1hh1tzc the problem remains.
Returning the value makes no difference as it will be optimised to "return 28".
In fact because you don't do anything with the return value it may not execute the function at all.
I came up with another example to avoid unused and predictable variables jsfiddle.net/2830emgk and the problem persists. I'm still not sure about the root cause. However your answer and the video made clear that my benchmarking attempts are very naive and quite useless since I might not be able to isolate the problem and the JIT will mess with my results and the tests I made to form my premise.
Indeed, it's very hard to write a microbenchmark that will produce a valid result.
1

It has to do with how symbol tables work. Symbol tables map symbols (e.g. variable names) to their values, like an associative array. What differentiates symbol tables from other types of associative arrays, like hash tables, is that they are hierarchical for simplicity and efficiency (each symbol table may have a parent). Symbol resolution begins in the current scope's symbol table, and works its way up to the root scope's, using either the value of the first, matching symbol or undefined if no symbol matches up to the root.

When variables are declared and referenced in the current scope, only one symbol table is hit: that for the current scope. However, when variables are declared in the parent scope and referenced in the current scope, then two hits to the symbol tables are required: one for the current scope which misses, and another for the parent scope which succeeds. Thus, referencing variables from the parent scope are approximately twice as slow as referencing variables from the current scope.

This article does a decent job on explaining symbol tables: http://www.tutorialspoint.com/compiler_design/compiler_design_symbol_table.htm

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.