JavaScript and Node performance coding tips to make applications faster
voidcanvas.com
voidcanvas.com
Global variables are slow... because of having to walk scope chains? Uh, no. Scopes are static properties (well, once the with statement was eliminated--now you know why that doesn't exist anymore): the compiler only has to look up the scope of the variable during compilation, which means it carries no penalty by the time you get to a JIT. The reason why global variable lookups can be slow is that global variables are often properties on objects with non-trivial access costs (such as a DOM window, if you running on a webpage), as opposed to an inaccessible lexical environment where all references are statically known.
(It's also somewhat disingenuous that all of the coding tips are about keeping v8 happy, as if v8 were the only JS JIT that mattered or v8 characteristics were somehow universal in JS JITs, neither of which is the case).
Running through the some of other bits:
* I'm not sure homogeneous arrays is an optimization benefit for other JITs than v8.
* Actually, traditionally JIT compilers focus on hot loop nests as well as functions. The original TraceMonkey JIT (first JS JIT) considered loops a starting traces, for example.
* Changing shape of objects does matter for monomorphic calls. Although not all JITs are as insistent on monomorphism as v8.
* True, but isn't this obvious?
* Last I checked (admittedly quite a while ago), JITs tended to optimize based on shape, not class. So there's no difference in performance between the two cases.
* Or you could use for/of. Or you could just never use sparse arrays because no JIT likes having these holes. I thought you were giving performance tips, it seems weird not to mention one of the more universal ones here.
* Try/catch is generally free if you don't throw an exception.
I could go on, but it's not worth it at this point...
Here is a perf test with various v8 versions (as you noted not only JIT but going going with the authors favorite).
but since async/await relies on the use of try/catch, last november's (october?) v8 release can optimize try/catch.
plus, it was only ever a v8 issue, not an issue in javascriptcore nor tracemonkey.
I believe all major JavaScript engines now optimize try/catch.
And SharedArrayBuffer is now disabled by default due to Spectre. I don't see it coming back soon either.
Also, I dislike this article because it recommends these things "always". "Always use forEach", "always avoid delete", "always...". Not to mention it's yet another v8 specific set of optimizations.
The biggest takeaway from the latest batch of optimizations I read about is this:
Write idiomatic JavaScript code!
Secondly:
Just because you're working with a dynamic language doesn't mean you should abuse it. Don't write functions with weirdly changing parameter arities, or dynamically change the types of variables.
Finally:
These things are all hints and micro-optimizations by themselves. Don't prematurely optimize at the cost of readability and maintenance. Profile and only fix things that need fixing!
I was not aware of the browser deprecation of Shared array buffer. But this (or something like this) is a part of the ECMA proposal which is in stage 4. https://github.com/tc39/ecmascript_sharedmem
I said using forEach (or in-builts) are always a good practice. Now like you have to. For an example, if you have an array with no holes in it, for is better performant then forEach.
I disagree, using try/catch/finally for their intended purpose (exceptional cases) is going to be preferable to trying to replicate their function on your own. Even if just from a semantic standpoint (is semantic the word i'm looking for here? it feels wrong...).
>But this (or something like this) is a part of the ECMA proposal which is in stage 4.
I really dislike the idea of true shared memory for the extreme vast majority of cases, and i'm not particularly happy about it's inclusion into browsers, so take what I say here with an appropriate amount of salt. I personally think spectre is only the first of many many problems it will bring if fully integrated. Transferable objects are a much cleaner and safer pattern to use in most cases, and they are here today in all major browsers. I do admit that having shared memory available is a massive bonus to some kinds of problems, but I feel the net impact will be negative. That being said, until a fix for spectre is either in hardware, or they find a way to solve it in software that doesn't destroy SharedArrayBuffer's performance, it's not coming back. And I don't see those happening within a few weeks/months in all honesty.
>I said using forEach (or in-builts) are always a good practice.
That was only one example of many in the article that I felt was making advice which works on one engine (v8), in a well-defined set of situations (sparse array, hot code paths, no async/await in the case of forEach), into a general truth. Not many things are universally true, and spelling out the when, why, and where you should apply this advice will go a long way toward teaching others rather than just giving people a list that they can haphazardly apply.
Like your advice to avoid for...in. If you are iterating over enumerable properties, there is no reason to avoid it. It's slow, because that operation is slow, and most attempts to do it yourself more quickly will either fail, or will have edge cases that may introduce bugs. It's good advice in hot-path code, but shouldn't be universally followed. Especially when the alternative to a few lines of well-understood code is a mess of if statements, key lookups, and undefined/null checks.
// for ... of
for(let node of nodes){
console.log(node);
}
// forEach
nodes.forEach(node => {
console.log(node);
});However it is slow currently in all major browser engines. For fast-path code, you should reach for something else (like a for(;;) loop), but for most other loops I always tend to reach for for...of for it's ease of use and compatibility with iterators makes it a pleasure to use!
At least https://jsperf.com/for-vs-foreach/293 shows that there is no significant difference in for of vs forEach in my version of chrome. There is an insignificant difference acoording to which forEach is actually slower. (One forEach case in that benchmark is only faster because it does something different.)
Edit: looking at the comparison at the bottom, it seems like forEach is actually significant slower than for of since chrome 61.
Thanks for that!
There are many reasons it is faster, but it also has the nice property of working sensibly on any iterable object rather than forEach that only iterates by index, and has an annoying ‘if (index in object)’ in the loop.
the for of is a loop.
at any rate it seems the kind of difference in legibility that would vanish with very little familiarity, I would expect for most people developing JavaScript regularly that forEach would be more legible - even with the arrow function - because it is the construct they encounter and use more often day to day.
Do you mean it's faster? (Don't use performant: use fast or faster instead.)
Look on the bright side: at least the article isn't titled "best practices"...
Justifying forEach (which is several times slower than a for loop) with sparse arrays (which are an anti-pattern because they take you out of "fast elements" mode in V8 at least) is laughable in a sad way. It also has no "break" functionality , which is important if we are talking about performance (i.e. when iterating the array to find a specific member).
The advice for using array literals to insert elements is also bad for the similar reason that it makes it easy to create sparse arrays.
That and "i>arr.length" in the example means the for loop will run exactly 0 times! ;)
Using "filter", "map" and "reduce", by the way, is also slower than using a for loop, even if broken out into a separate function for purposes of chaining. This is because they call a function on each iteration and function calls (even with optimisation) are inherently more expensive. So, use them only when this difference in performance does not matter.
The closure / timer example is just so convoluted. Of course the closure will keep "bar" in scope - that's what closures do! The "foo" object doesn't somehow magically own "bar" it just has a reference to it, same as the closure. If something still references an object then GC will not touch it.
Similarly for the event listeners advice, which is also badly worded.
And if you do need to write a timeout-based loop, I respectfully suggest the following construct:
const interval = 1000;
var timer;
(function loop () {
... do stuff ...
timer = setTimeout(loop, interval);
}());
Now you can even use "rewire" to control the interval during your unit tests - bonus!The Arrays vs Objects thing is just shallow. In reality, the advice is "it depends". If you need to iterate, use an Array. If you need to access by key, use an Object (or better a Map). If you need both (and this frequently happens in my experience) then you have to decide depending on the size of your structure.
Here's a performance tip: learn how to measure your damn code in the context of the website, because the JIT depends on heuristics that involve the context of the whole code.
Open website -> Open Dev Tools -> Performance -> Do Stuff for a reasonable amount of time.
Then you can actually reason about what causes the bottleneck based on the numbers. If you're on Chrome, you can even look at the source code after using the performance tab for a while, and see a line-by-line breakdown of where most of the time was spent, and find the hot path down to the individual expression or statement. Fix real bottlenecks, not hypothetical ones.
The article is woefully misinformed regarding `forEach` and I agree that the array methods in general are slower [1], but `some` will bail out on the first true value returned. To be sure:
[1,2,3,4,5].some(function(n) { console.log(n); return n>=2; });
will not run the callback function after processing the 2Also, "some" still has the performance penalty of an extra function call per iteration (as I know you know based on your linked comment) and we are talking about performance advice here.
But good point - I've never actually used "some" before, so I learn something new today - thanks :)
In the environments most of this would be used on people do not stick around long periods of time, therefore any simple optimization that one can do in the preliminary development of an application is not premature.
But this article sucks, as do most articles with a list of coding tips for a particular language/platform.
Be careful with this, as dictionaries are not really O(1), in the worst case, it can be actually O(n). When your code heavily evolves around the 'fact' that it's O(1) try to make a version with a simple array iteration and compare which is faster.
Very approximate and dependent on the case obviously, but I always find it surprising how far you can go with just a simple array.
From 100 to 1000 items the array performance is not massively worse and shouldn't matter much but a dictionary can beat it (by a tiny bit).
If you're over 1000 definitely use a dictionary or tree.
And, again, for the last time, Node.js is not actually single threaded and may whatever deity help you if you choose to start additional threads without understanding how libuv works. This isn't java, the answer isn't to throw more threads at it.
Honestly, it's this kind of stuff that really hinders javascript. So many people write "expert" articles that show fundamental misunderstandings about the core of JS (including setTimeout vs setImmediate!)
In practice you can avoid this problem if your function wrapping a timer is not a method and the callback of the timer is a recursive call to the very function wrapping the timer. In that case there is no object to access to get to the timer's function and secondly the this function never nullifies or removes from garbage collection until the last timer is called.
> setImmediate over setTimeout(fn,0)
Yes, agreed, but I wouldn't ever run timers in a loop to achieve parallel asynchronicity. I would use native Node methods to achieve this. I would however use timers to achieve sequential asynchronicity, also known as polling, which is where setTimeout would be beneficial. Sequential operations likely to always be slower than parallel operations, but sometimes you only just need intermittent delays.
I agree that these recommendations in the article are really good ideas.
The most helpful resource I've found about v8 specific performance is the bluebird optimization killers wiki: https://github.com/petkaantonov/bluebird/wiki/Optimization-k... wiki, and the v8 bailout reasons repo: https://github.com/vhf/v8-bailout-reasons
You are correct that these no longer apply to the most recent Node versions. Crankshaft is still being used in Node <8.3.0.
Crankshaft got totally disabled starting from V8 5.9. Node 8.2.1 uses V8 5.8.283.41, Node 8.3.0 uses V8 6.0.286.52.
I'm pretty sure there is still a big number of projects running on Node <= 8.3.0.
I'll make it clear in this project README though, whenever I find time. :)
Write readable, natural code, and compilers are more likely to eventually optimize it, even if perhaps they do a bad job today. Only contort your code to improve performance if you have profiles showing that you need to do so, and even then, see if there's natural code that works first.
The asterisk comes from the costs of having multiple shapes for a given variable. V8 (to my recollection, it could very well be out of date) generally has a performance cliff between monomorphic and polymorphic functions: if the added strictures of typechecking is giving you monomorphic functions, it could see a performance improvement. Other JITs (again, to my recollection) are generally happier to have polymorphic functions where the degree of divergence is still small (say, two or three shapes), although having 1000 shapes is going to be unhappy for everybody.
Note that this discussion is effectively microoptimization-level discussion: don't rearchitect your code to enforce monomorphism at the expense of clarity unless you have profiling evidence that the performance is necessary.
haha, async await needs that by design!
const result = await doStuff().catch(errorHandler);
// =>Promise {<resolved>: 1}
As you can see, the variable contains a promise returned by the .catch. It's not equivalent to try/catch (in that case the variable won't be defined for one thing), but it does allow for error handling without using try/catch if one is inclined to do so.
In practice, I've been using async/await extensively in Node.js/Express, and I'm yet to write a single try/catch. In Express, I simply use a wrapper function that catches promise rejections and siphons them to error handling middleware:
const asyncIt = fn => (req, res, next, ...args) => fn(req, res, next, ...args).catch(next);
My 2 cents from working over 5 years with NodeJS is to be careful when you sacrifice code readability over performance.
`.forEach`, `.map`, `.filter` are way more readable / maintainable than a 20 lines `for loop` with n+ var assignments.
As for `try..catch`, I use ( readability - again ) to follow a Promise chain.
return new Promise( resolve => resolve( JSON.parse( req.body ) ) )
.then( body => db.update( body ) )
.catch( err => /* will catch JSON.parse as well as db.update */ )Errr...
Same with window in a browser context. You could still have a "window" variable placed between your execution context and the global context.