13

In the bluebird docs, they have this as an anti-pattern that stops optimization.. They call it argument leaking,

function leaksArguments2() {
    var args = [].slice.call(arguments);
}

I do this all the time in Node.js. Is this really a problem. And, if so, why?

Assume only the latest version of Node.js.

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • The point they are trying to make is that arguments are crucial for optimizations that V8 does. Given how you can mutate [arguments in a function](http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language) in ways similar to pass-by-reference, this means V8 has to recheck already existing signatures again. Because of this V8 would never be able to guess the data-types to optimize function calls. – user568109 May 07 '14 at 10:21
  • @user568109 some uses of `arguments` are supported by the optimizing compiler - those that don't require a materialized `arguments` object, such as arguments[i] or arguments.length (where i is valid index) – Esailija May 07 '14 at 10:22
  • Yes, mentioned as safe usage at the end of the section 3 of [documentation](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments). – user568109 May 07 '14 at 11:00

2 Answers2

13

Disclaimer: I am the author of the wiki page

It's a problem if the containing function is called a lot (being hot). Functions that leak arguments are not supported by the optimizing compiler (crankshaft).

Normally when a function is hot, it will be optimized. However if the function contains unsupported features like leaking arguments, being a hot function doesn't help and it will continue running slow generic code.

The performance of an optimized function compared to an unoptimized one is huge. For example consider a function that adds 3 doubles together: http://jsperf.com/213213213 21x difference.

What if it added 6 doubles together? 29x difference Generally the more code the function has, the more severe the punishment is for that function to run in unoptimized mode.

For node.js stuff like this in general is actually a huge problem due to the fact that any cpu time completely blocks the server. Just by optimizing the url parser that is included in node core (my module is 30x faster in node's own benchmarks), improves the requests per second of mysql-express from 70K rps to 100K rps in a benchmark that queries a database.

Good news is that node core is aware of this

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • How could I possibly know what is hot or not? Is that wikipage the only resource on this? This is absolutely new to me. Never heard of this. In fact, I have patches to submit here. This is pretty rudimentary stuff that I've been doing wrong -- and [some people whom I regard as being much better than me](https://github.com/visionmedia/node-thunkify/pull/12) – Evan Carroll May 07 '14 at 19:03
  • 1
    @EvanCarroll "hot function" means a function that is being called a lot, you know what functions are called a lot by having an understanding of the program, using profilers or counters and so on. – Esailija May 07 '14 at 19:10
  • aww, I thought hot meant optimizatable in this context. The question remains, is there a more authoritative source on what what is optimizable in V8 than the wiki page for bluebird? Is there a description anywhere of %GetOptimizationStatus 1-6 and what they mean exactly, and what causes those statuses. – Evan Carroll May 07 '14 at 19:14
  • @EvanCarroll that would be V8 source code, nobody official maintains an accessible list like the wiki page does – Esailija May 07 '14 at 19:15
  • 1
    @EvanCarroll getoptimizationstatus is "documented" here https://github.com/v8/v8/blob/master/src/runtime.cc#L8721-L8753 – Esailija May 07 '14 at 19:16
  • 1
    V8 internals are hard to find. I believe it optimizes functions which are hot and stable. Stable meaning they have consistent pattern in function call datatypes. Meaning their argument does not change much (or in a pattern). Based on this they profile functions accordingly -> premonomorphic (initial), monomorphic (fixed arg), megamorphic/polymorphic (more than once). I could find few links for this http://cs.au.dk/~jmi/VM/IC-V8.pdf and http://wingolog.org/archives/2011/07/05/v8-a-tale-of-two-compilers. – user568109 May 08 '14 at 05:35
  • @user568109 those are not properties of functions but inline caches. A function is attempted to be optimized as long as it's being called a lot, it doesn't have to be stable - in fact function can have even unexecuted portions and still get optimized (in which case if those code paths are reached in optimized code it will just do a soft deoptimization) – Esailija May 08 '14 at 09:52
  • @Esailija Thanks for pointing that. The motive for my earlier comment was that, stable function signatures will give optimized inline caches. If the signature changes it would get deoptimized and optimized later if found hot. But if singature varies too much (which can happen due to unsafe argument usage), it will remain generic. Compiler would bail out, constantly trying to optimize-deoptimize the function. Am I correct about this. – user568109 May 08 '14 at 10:10
  • @user568109 unsafe `arguments` usage is determined statically from syntax, it will never be optimized at all if there is such usage. – Esailija May 08 '14 at 10:33
  • Didn't know that, the compiler won't pass around argument as another object. And simply refuses to optimize the function. A must [know](https://news.ycombinator.com/item?id=2770983) for all node users. – user568109 May 08 '14 at 11:18
  • Looking around I came across this which was helpful: http://floitsch.blogspot.com/2012/04/optimizing-for-v8-assembly.html – j03m May 29 '14 at 15:01
-2

Is this really a problem

For application code, no. For almost any module/library code, no. For a library such as bluebird that is intended to be used pervasively throughout an entire codebase, yes. If you did this in a very hot function in your application, then maybe yes.

I don't know the details but I trust the bluebird authors as credible that accessing arguments in the ways described in the docs causes v8 to refuse to optimize the function, and thus it's something that the bluebird authors consider worth using a build-time macro to get the optimized version.

Just keep in mind the latency numbers that gave rise to node in the first place. If your application does useful things like talking to a database or the filesystem, then I/O will be your bottleneck and optimizing/caching/parallelizing those will pay vastly higher dividends than v8-level in-memory micro-optimizations such as above.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • Not knowing the details on a question that asks *why* is not that useful. You're just taking it on faith that this optimization in bluebird was for Node, and not something else. Or, that it even works at all. – Evan Carroll May 07 '14 at 06:01
  • I know this is not the comprehensive answer you may be hoping for and I hope someone else posts such an answer. It's not "faith" but it is indeed relying on the bluebird docs which are pretty explicit about v8 in particular. As to whether it works at all, given your rep you are clearly capable of writing 2 .js files to prove/disprove that to your own satisfaction. You should clarify whether you mean "why does v8 refuse to optimize these functions" vs "why is this a performance concern". The answer to the former is usually "they haven't figured out a safe way yet". – Peter Lyons May 07 '14 at 06:12