Vyacheslav Egorov, a compiler engineer who worked on V8 and the Dart VM, writes about JavaScript engine optimization and benchmarking.
HN commenters say he reliably produces high-quality V8 material, call the site full of hidden goodies, and recommend it for learning how V8 optimizes code.
Not what I said. I said to avoid /hand-optimizing/ code.
Trying to write performant code is good. Some examples of advice on that front:
- use an appropriate algorithm, and make sure the logic is free of bugs. For example, accidental out-of-bounds array accesses can really hurt performance.
- avoid needless computations. Don't do something (complex) twice if doing it once is enough.
If this sounds like "use common sense/generally good design principles", that's because it is (and hence the agreement with many of parent post's points that are simply and straightforwardly good design principles).
Let's take the example of "remove function calls". It's true that each call costs a few instructions, but unless the function you're calling is tiny, that overhead won't be measurable. If it makes sense for readability/maintainability/testability to split your code into functions, then by all means do it!
Another example is the "trick" to write "for (var i = 0, len = array.length; i < len; i++)" instead of the simpler "for (var i = 0; i < array.length; i++)". As http://mrale.ph/blog/2014/12/24/array-length-caching.html (from the original V8 team) explains in great detail, what seems like a no-brainer can actually do the opposite of what you'd expect --- and at the same time, won't make any difference whatsoever in most real code (i.e. aside from microbenchmarks).
Yet another example is https://www.youtube.com/watch?v=UJPdhx5zTaw. You can spend all day on it, but at the end of the…
I understand that. Take a read of this: http://mrale.ph/blog/2013/03/28/why-asmjs-bothers-me.html.
I have a lot of respect for Egorov, and I agree with his views on this matter quite entirely.
The sole benefit of embedding a bytecode in a language is that you get the side effect that it can run anywhere that language runs. But in general, this implies performance penalties, especially when the language is relatively slow to begin with.
But in this case, this demo is made usable (ie: the impressive part) by writing a compiler specifically for the bytecode itself. So it works well in spite of the fact that it's a bytecode embedded in JS, not because of it. The really cool things about this demo are precisely 2 things:
1. asm.js makes running C++ code that would render to OpenGL in the browser a possibility, and with relatively good performance. Kudos to Mozilla.
2. It runs on browsers that don't know about asm.js, but it's effectively an emulated machine, and it's slow.
I just take issue with calling this an example of what JavaScript/HTML5 can do. I think seeing an application written in JS using WebGL or Canvas as something much more impressive, and there are Q3 renderers that are natively JS that run with amazing performance in the browser. This is just dressing up the fact that turing completeness is a thing and calling attention to it. The real thing to get from this demo is that there are enormous benefits to having a bytecode for the web over trying to optimize a…
Only Firefox uses a separate special interpreter (Odin Monkey) for asm.js - Chrome's asm.js improvements come from optimizing the same V8 JS pipeline. Having a separate compilation pipeline can be viewed as a disadvantage since any optimizations for speeding up asm.js doesn't improve JS performance: http://mrale.ph/blog/2013/03/28/why-asmjs-bothers-me.html
> It will still not be comparable to asm.js, because asm.js runs in all browsers without the site author having to do anything
Dart web apps will continue to run in all browsers just as it runs today through Dart2JS, it even has great source maps support which lets you debug native Dart code even though it's running transpiled JS. There's nothing special the "author" would have to do, it already exists as part of the project template, e.g. if the browser has a Dart VM it will run the Dart code natively, otherwise it will run the Dart2JS version. Since some Dart benchmarks http://www.dartlang.org/performance/ show Dart2JS outperforming handwritten JS in V8 even with the overhead of different semantics, I'm assuming that Dart2JS compiles to efficient JS that developers wouldn't write.
> because asm.js runs in all browsers
runs is a technically interesting term, there are some asm.js demos that you would not attempt to run without asm.js specific optimizations (i.e. not Firefox) since it would be unusably slow. This is visible in the Epic Citadel http://www.unrealengine.com/html5/ flagship asm.js demo where it needs a…
Commented this on the blog; cross-posting here:
V8 turns out to have a ton of internal string representations. They don’t affect semantics (the general point that JS string functions think in UTF-16 is valid) but they’re interesting.
V8 apparently stores all-ASCII strings as ASCII, so stuff like HTML tag names or base64 blobs doesn’t double in size.
Like Go, V8 lets you take a substring as a pointer into the larger string; the internal class is called SlicedString, but from JS-land you don’t see anything different from a string literal. As in Go, keeping a short substring of a long parent string keeps the whole parent ‘alive’ across GCs so sometimes folks will be surprised all those bytes are still allocated.
Unlike Go, V8 has a ConsString type, so concatenating strings sometimes doesn’t really immediately copy the underlying bytes anywhere. Building a string with a loop that runs str += newPiece probably goes faster than expected because of this. [It turns out it flattens the string the next time you index into it, or at least used to, which has some perf implications of its own: https://gist.github.com/mraleph/3397008]
That’s mostly from this post by a member of the Dart team http://mrale.ph/blog/2016/11/23/making-less-dart-faster.html ; his blog has a lot of interesting stuff about how these fine-tuned language implementations really work.
Kind of amazing the lengths V8 (and other JS engine) teams went to to make the code they saw in the wild work well.
Here are a bunch of blogs from my RSS reader that I think are worth reading. Some of them haven't updated in a while, but the archives may be worth a look anyway.
* Blogs with a math focus:
http://blog.sigfpe.com
http://bit-player.org
http://mathr.co.uk/blog/
https://www.johndcook.com/blog/
* Blogs with a programming languages focus:
https://existentialtype.wordpress.com
https://bluishcoder.co.nz
http://blog.ezyang.com
http://akkartik.name
http://lambda-the-ultimate.org
http://mrale.ph
* Blogs with a UI design focus:
http://www.brandonwalkin.com/blog/
http://ignorethecode.net/blog/
http://interuserface.net
* Blogs with a graphics / 3D rendering focus:
http://c0de517e.blogspot.com
http://diaryofagraphicsprogrammer.blogspot.com
https://directtovideo.wordpress.com
http://graphicrants.blogspot.com
http://www.reedbeta.com
http://www.realtimerendering.com/blog/
https://blog.demofox.org
http://eelpi.gotdns.org/blog.wiki.html
* Other interesting / more general programming blogs (many of these have a "systems" focus):
http://danluu.com
http://ewontfix.com
http://chneukirchen.org/blog/
http://neverworkintheory.org
http://apenwarr.ca/log/
https://www.snellman.net/blog/
https://www.pvk.ca
http://psy-lob-saw.blogspot.com
https://randomascii.wordpress.com
http://fastcompression.blogspot.com
https://fgiesen.wordpress.com
http://blog.plover.com
http://chneukirchen.org/trivium/
http://rachelbythebay.com/w/
In JavaScript every object is basically a hashtable. In order to get acceptable performance JS engines build type descriptions ("hidden classes" or sometimes just "maps") behind your back. That is, if you create a JS object (hashtable) with fields `x` and `y` it will create a hidden class with those two fields. This way it can access the fields efficiently. See http://mrale.ph for much more information on JS engines.
These hidden classes are built on the fly: say you add `x` to a instance to get an object with a configuration that was never seen before. In that case, the engine creates a new description with that new field and remembers the transition (so that further objects with similar configurations can reuse the same hidden class).
Keeping track of these hidden classes takes time and space. If you actually use the JS object as a hashtable, then there will be lots of unnecessary hidden classes, that will never be used again. However, there is a trick to tell the engine that an object is actually a hashtable (and shouldn't be optimized as if it was an instance of a class): when deleting a field from an object v8 (and probably all other engines) assume that the object is used as a hashtable and don't create these hidden classes anymore.
Dart2js uses this trick to avoid this cost some important big hashtables (`B`, `C`, ...).