
JavaScript and Node performance coding tips to make applications faster - osopanda
http://voidcanvas.com/javascript-performant-coding-tips/
======
jcranmer
The quality of this article is readily apparent in the first recommendation.

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...

~~~
thealfreds
The try/catch wasn't always that case. I'm having trouble finding the blog
from one of the v8 developers who went into details about it and other
optimizations. It was on HN a while back.

Here is a perf test with various v8 versions (as you noted not only JIT but
going going with the authors favorite).

[https://github.com/davidmarkclements/v8-perf](https://github.com/davidmarkclements/v8-perf)

~~~
jcranmer
My recollection is that the nonperformance of try/catch is something specific
to the v8 JIT. Historically, the zero-cost exception model of try/catch has
you do absolutely nothing with the data until someone throws an exception, at
which point you work out how to map the return address to the appropriate
catch handler.

------
Klathmon
>The try/catch is a very costly operation. You should write your code in such
a way that exceptions are not uncertain.

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.

~~~
metalshan
Try/catch point is an in-general thing. Irrespective of language. Always
better to handle errors by yourself.

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](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.

~~~
mschuetz
I find "for of" to be much more legible than "forEach". Not much difference in
general but the "for" the beginning immediately tells me that there is a loop
here, whereas I have to read more thoroughly to identify something as a loop
in the forEach case.

    
    
      // for ... of
      for(let node of nodes){
          console.log(node);
      }
    
      // forEach
      nodes.forEach(node => {
          console.log(node);
      });

~~~
Klathmon
For...of is fantastic and I use it quite often (especially in async/await
codebases as it doesn't create a new function context!)

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!

~~~
mschuetz
Are you sure about that? "for of" is something that I'd expect a js compiler
to handle exactly the same way as forEach. I'd see how it might have been
slower right when it came out but browser vendors are quick in optimizing
their compilers.

At least [https://jsperf.com/for-vs-foreach/293](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.

~~~
olliej
Lots of work went into making the perf of for(of) fast - after the initial
“make it semantically correct” bits.

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.

------
gbuk2013
This is a very mediocre article that mixes some good advice with misleading
and frankly bad things.

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.

~~~
sheetjs
> 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 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 2

[1]
[https://news.ycombinator.com/item?id=7938173](https://news.ycombinator.com/item?id=7938173)

~~~
gbuk2013
"some" !== "forEach" ;)

Also, "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 :)

------
z3t4
While the points are correct, they are bad advice, except for the last point:
prefer O(1) over O(n) because all (premature) optimizations are evil! What
works in one engine for example (Firefox) might not work in Chrome, or the
next version of the same engine. So write stupid simple naive code until you
actually _need to_ optimize. Then go for algorithms first, changing something
from O(n) to O(1) will have a bigger impact then for example creating static
binaries (which most of the time will be slower because there will be no run-
time optimizations)

~~~
bryanrasmussen
The original premature optimization quote was specifically about spending a
lot of time optimizing complicated algorithms in an environment in which
computation was often business required if used so as a consequence one could
count on people staying around more than insignificant amounts of time to get
a response.

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.

------
fvdessen
I'm confused about the setTimeout example; the code as presented is creating
an infinite loop, so it does not surprising that the object used in the loop
is not garbage collected. But I would expect references inside a timer to be
collected once the timer has expired. Do they not ?

------
TN1ck
> Avoid O(n), try O(1)

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.

~~~
BillinghamJ
I believe a good rule of thumb is that looping over an array will generally be
faster than using a hash map until you have around ~1,000 items.

Very approximate and dependent on the case obviously, but I always find it
surprising how far you can go with just a simple array.

~~~
zaarn
From experience I would say it's closer to 100 items, though this is using Go
as benchmark. 100 items is also what I would expect considering CPU caches.

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.

------
vorpalhex
This article is quite incorrect. Some of these points were valid many versions
ago (try/catch did have a cost with it several V8 majors ago, but that has
since been resolved).

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!)

------
austincheney
> Closure & timer – a deadly combo

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.

------
nwhatt
Any team with enough developers using Node should develop a handful who can
understand the v8 internals and help troubleshoot real-world performance
problems.

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...](https://github.com/petkaantonov/bluebird/wiki/Optimization-
killers) wiki, and the v8 bailout reasons repo:
[https://github.com/vhf/v8-bailout-reasons](https://github.com/vhf/v8-bailout-
reasons)

~~~
jcranmer
The best advice I can give about performance engineering is "all performance
guidelines are bullshit." Compilers and JITs are constantly improving, which
means any specific guideline like "do X to avoid a JIT bailout" isn't
necessarily true going forward. Furthermore, the benchmarks that compiler
writers use to assess the utility of their optimizations aren't hand-optimized
unreadable kludges, it's generally typical applications.

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.

~~~
tejinderss
Do you think adding type annotations or using typed js (typescript/flow), the
optimisations could be improved? Since the types are providing more metadata
about the code?

~~~
jcranmer
No (with an asterisk). The state-of-the-art for JS optimization already
computes shape information (roughly equivalent to typing) and feeds that into
optimizations.

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.

------
brwsr
> Avoid try/catch

haha, async await needs that by design!

~~~
maga
It's not required, you can use .catch since it's just normal promises:

const result = await doStuff().catch(errorHandler);

~~~
dcherman
So what do you think your result variable contains in the event that the
promise is rejected? Is this semantically equivalent to try/catch?

~~~
maga
(async () => { const result = await Promise.reject(1).catch(e => e); return
result; })()

// =>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);

------
drinchev
These tips are all great. Thanks for the article.

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 */ )

------
partycoder
If you want to squeeze more performance out of JavaScript, at some moment you
will have to deal with the gatekeepers of performance: inline caching, hidden
classes, deoptimizations, garbage collection.

------
maga
I feel like these "N performance tips" make it onto the main page mostly
because of the commenters coming up with counterpoints to each "tip".

~~~
iaml
Real performance tips are always in comments!

------
cmollis
Promise.all vs sequential awaits is a good tip but Only if the results of the
awaits are independent (obvious). I see that all the time. It’s easier to see
how inefficient that is when you’re chaining .then().. await hides that and
gives the impression that it’s parallel.

------
mulrian
_Second point is, global variables are not cleared by the garbage collector.
So if you continuously add more and more global variables (which are not of
future use), it will cause a memory leak._

Errr...

~~~
Skinney
It's an easy thing to do in JS, it happens if you forget the `var`.

~~~
Klathmon
only when not in strict mode, which is becoming more and more rare, especially
in javascript codebases (as opposed to one-off scripts)

------
aforty
What if I specifid the `global` scope, like `global.SOME_VAR`? Will that skip
the expensive search of the parent nodes?

~~~
Sawamara
It cannot. It still has to check whether there is a local variable in any
other scope that is above the one currently being executed, all the way to the
top where it finds global.

Same with window in a browser context. You could still have a "window"
variable placed between your execution context and the global context.

------
Lord_Zero
"Create class for similar kind of objects" would using ECMAScript 2015 classes
be acceptable?

------
brudgers
Profiling might be a useful addition to the list,

