8 ms·
> How to write optimized JavaScript This is all sensible advice if you're interested in writing fast-enough code, but I do find there's a lack of material for
by Veedrac 9y ago
> How to write optimized JavaScript
This is all sensible advice if you're interested in writing fast-enough code, but I do find there's a lack of material for people who want to write fast Javascript. Pretty much the only thing I've found is the post by Oz[1], though I really don't want to have to compile Chrome.
For an example, I have a method in Javascript that does a mix of (integer) arithmetic and typed array accesses; no object creation or other baggage. I want it to go faster, and with effort I managed to speed it up a factor of 5. One of the things that helped was inlining the {data, width, height} fields of an ImageData object; just moving them to locals dropped time by ~40%.
Yet after all this effort, mostly based on educated guesses since Chrome's tooling doesn't expose the underlying JIT code for analysis, the code is still suboptimal. There's a pair of `if`s that if I swap their order, that part of the code allocates. How do people deal with these issues? A large fraction of this code is still allocating, and I haven't a clue where or why.
Perhaps I'm asking too much from a language without value types (srsly tho, every fast language has value types), but what I want is clearly possible: ASM.js does it! I don't really want to handwrite that, though.
[1]: https://www.html5rocks.com/en/tutorials/performance/mystery/ https://www.html5rocks.com/en/tutorials/performance/mystery/ (E: This link is actually written from a different perspective than the one I read, but the content is the same.)
- pjmlp 9y agoYes, it would be nice to just have some config flags to enable it on the debugger.
- inglor 9y agoYou can ask V8 to dump the assembly or intermediate representation or you can just run it with --trace-deopt and it'll tell you most of the time (generally) what's slowing your code down. > I'm asking too much from a language without value types JavaScript has value types, what it doesn't have is user defined value types. BigInts are coming (stage 3 already!) though.
- avaer 9y agoI have some experience with this stuff, and my go-to advice is: - Never construct or mutate objects in a loop or recursive call. - Preallocate the max of what you'll need at load time, then index into it. - Prefer typed arrays over objects. - Don't use strings. Work with numbers. - Remove function calls. - Do not call a function with differently shaped arguments. - If the JS engine can't prove a number is a 32 bit int, you'll take a large perf hit. - Break out your algo into a worker. Not only does it keep things responsive, but it might actually be faster due to less GC pressure. - The web stack is full of frightening gotchas that you'll discover. Recieving large data on a WebSocket can halt your DOM rendering. Leaking a Promise callback can completely blow a function into interpreter mode. And other fun times. Finally: If you're hand optimizing and not getting sufficient results on your preferred browser, accept that you're screwed. The next browser will break your assumptions. Rethink your algorithm and data flow, or move it elsewhere, like a worker or server. I do agree the tooling for this stuff isn't great, but I found Chrome's line-by-line perf counts to be excellent hints about where you could do better. It's often surprising but obvious in retrospect.
- inglor 9y agoIn V8, objects are actually arrays (sort of, they have an indexed store) - so doing `this[0]` is about as fast as doing it with an array (certain array things like for... of iteration are optimized though - but only if you haven't changed the prototype).
- nopenopenope8 9y agoThis is incorrect. Arrays are implemented as a type of object, but you shouldn't worry about that. Hashmaps and arrays both have ~O(1) random indexing. If you need an array, don't use an object.
- inglor 9y agoObjects have a length overhead. At Bluebird we used objects and used a bitmask on the length so it's fast. (We use less slots than an array actually) I was just pointing out that Objects weren't slow for indexed access.
- deleted 9y ago[deleted]
- avaer 9y agoIn case it wasn't clear, when I said prefer typed arrays over objects, I literally meant Typed Arrays [1]. These are not like arrays _or_ objects. They're faster than either, since there are less corner cases and type ambiguities. [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays https://developer.mozilla.org/en-US/docs/Web/JavaScript/Type...
- fenomas 9y agoIs that definitely true? Some time ago I was tuning code that did lots of math on a big array of floats, and I found that using Float32Arrays was moderately but measurably slower than just using []. This may have been before TurboFan, though.
- Arelius 9y agoThe problem however is that JS optimization is very sensitive, depending on the browser vendor, version, and execution context, optimization advice can change dramatically. Back when I worked on a perf sensitive JS codebase, we had a perf test suite, and depending on the details, often times locals were faster, but sometimes objects, and on a different browser it was arrays. And if you accident hit an ask.js opt it could be typed arrays, but mostly typed arrays were significantly slower. And when a new version came out this could all invert.
- fenomas 9y agoI don't think there are any really solid approaches here except to use IRHydra [0], which lets you decompile v8's optimized version of a function into an assembly-like representation. But I'm pretty skeptical of whether anyone but v8 engineers can really make use of it. I think the only workable approach is what you're doing - have an approximate idea of how the compiler works and make intelligent guesses about what will help. But when you're tuning at the level where changing the order of if statements makes a difference, you're at the level where next month's update may reverse your fix, so in a lot of cases it'll be questionable whether it's worth the effort. [0] IRHydra - http://mrale.ph/irhydra/2/ http://mrale.ph/irhydra/2/
- pkozlowski_os 9y agoIs the IRHydra is not working with the latest V8 pipeline (Ignition + Turbofan), AFAIK: https://github.com/mraleph/irhydra/issues/52#issuecomment-258668581 https://github.com/mraleph/irhydra/issues/52#issuecomment-25...
- mncharity 9y agoIgnition/Turbofan has a new IR - IRHydra 1 and 2 won't help. For Turbofan there's https://github.com/v8/v8/tree/master/tools/turbolizer https://github.com/v8/v8/tree/master/tools/turbolizer .
- mraleph 9y agoYou can still do d8 --trace-turbo \ --trace-deopt \ --code-comments \ --redirect-code-traces \ --redirect-code-traces-to=code.asm \ --print-opt-code \ your-file.js and load resulting turbo.cfg and code.asm into IRHydra. However most of the important features (like source and deopt-to-ir mapping) are broken. I used to repair things in my free time - but I don't think I want to do it anymore.
- mncharity 9y ago> I used to repair things in my free time Oh! Thank you for all your work. I noticed my current node wasn't dumping .cfg's, and assumed that data path had died with TF. > but I don't think I want to do it anymore. Nod. Programming languages - civilization critical infrastructure funded like kindergarten bake sales.
- pcwalton 9y ago> There's a pair of `if`s that if I swap their order, that part of the code allocates. How do people deal with these issues? A large fraction of this code is still allocating, and I haven't a clue where or why. Are you sure that allocation is a problem? Nursery allocation is extremely fast: it's just one overflow check and a pointer bump. Usually it's around 3-6 instructions. Allocation in modern generational GCs is in a completely different performance category than C-style malloc is.
- vanderZwan 9y agoNot GP, but as someone who has worked with image manipulation and typed arrays in JS (which seems similar to what the GP was doing) I can say that this often boils down to linearly looping through a typed array and doing very simple operations. You know, one of those "best case" scenarios for cpu utilisation where memory access tends to be the bottleneck. If the allocation happens inside a tight loop like that I can imagine it ruining performance
- Veedrac 9y agoPointer-bump allocations are fast in isolation, but if your code is doing one it means the compiler has gotten itself confused, and every time it gets confused it makes it harder for later code to be optimized. Compilers only really reason reasonably about value types (and even then you should never assume they're smart[1]). That said, I don't actually know allocation is the problem; it's a suspect, but unlike a native script I can't just run `perf` to get performance counters and assembly listings. All I really know is that the non-allocating order is a lot faster. [1]: If you can read assembly, this one's hilarious: https://godbolt.org/g/81qWoq https://godbolt.org/g/81qWoq
- tracker1 9y agoI find the Steve Souders' books [1] "High Performance Web Sites" and "Even Faster Web Sites" methods are still fairly practical, not all of it applicable to JS directly, but still. In the end, you'll need to test your changes in the most common browsers that affect you, because each browser's JS engine can vary a lot. The lookup cost you mention can be deceptively expensive, especially if you have anything resembling a deeper inheritance tree, or worse, proxies, getters/setters etc. In the end, a lot of it doesn't matter, until it does. [1] https://www.amazon.com/s/?keywords=steve+souders https://www.amazon.com/s/?keywords=steve+souders
- mncharity 9y agoREADME> Turbolizer is a HTML-based tool that visualizes optimized code along the various phases of Turbofan's optimization pipeline, allowing easy navigation between source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code. https://github.com/v8/v8/tree/master/tools/turbolizer https://github.com/v8/v8/tree/master/tools/turbolizer is like IRHydra, but for Turbofan. svn export https://github.com/v8/v8/trunk/tools/turbolizer turbolizer cd turbolizer python -m SimpleHTTPServer 8000 chromium-browser http://0.0.0.0:8000/ chromium-browser --no-sandbox --js-flags="--allow-natives-syntax --code-comments --trace-turbo" testfile.html mkdir out cd out node --allow-natives-syntax --code-comments --trace-turbo ../testfile.js cat testfile.js function add(a,b) { return a + b; } add(23, 44); add(2, 88); %OptimizeFunctionOnNextCall(add); add(2, 7); ls out ... turbo-add-0.json ...
- mraleph 9y ago> is like IRHydra It does 'IR' part of the IRHydra, but it does not give you another important feature - ability to see how your function evolved over time and figure out reasons for it. Loading files one by one in search for the right version is pretty cumbersome. I was always hoping that V8 will eventually get proper Dev Tools integration for compiler / runtime internals - and I think things have finally started happening in that area.
- mncharity 9y ago> cumbersome Yes, turbolizer currently lacks some of IRHydra/2's mature featurefulness. No pretty "you're deopt is here". I've no idea whether Google would welcome contributions or not. > I was always hoping that V8 will eventually get proper Dev Tools integration for compiler / runtime internals Wistful sigh.
- mncharity 9y agoThe current usefulness of turbolizer seems... mixed. Turbofan generated code is highly variable. Changes in argument types and optimization order can result in very different code being emitted. It's not like "all these cases were well optimized, but this one case wasn't, so let's fix/avoid that one". Instead, it's "well, that's a diverse mess of results, hmm...". Perhaps that will change. One project objective is apparently optimization with fewer performance cliffs. But another objective is a compiler which better serves low-end mobile devices - thus raising the bar for what optimizations are worth spending time on. There's a tension between those two. Also, CPU branch prediction makes interpreting the resulting assembly more interesting. Turbofan output has a lot of deopt guards. Tests that the assumptions used when writing the optimized code are still true. But say one bit of javascript optimizes to code with more guards than another. Do they matter? Maybe not, if the guards end up off mainline and hit cache. So turbolizer seems helpful if you have a some red-hot performance chokepoint. But at present, perhaps less so for providing more general guidance.
- Lerc 9y agoTo an extent I feel like there is a dividing line in the type of optimisations that I should aim for. I'd aim for Typed arrays and avoiding intermediary object creation, because those reduce the intrinsic work load (especially for things like float32 where you are giving a nod to the system to allow slightly worse results) Aything where the optimiser should be able to figure out what to do I'm much more hesitant. Inining variables is something that I don't feel should be neccessary. Anything that is just massaging the optimiser into making the right desicion runs the risk of making it worse on a different engine. That different engine may even be a later version of your current engine.
- metrognome 9y agoVyacheslav Egorov wrote an article[0] on monomorphic function calls and how they impact JavaScript performance. While it's very in-depth, it is over 2 years old at this point, so I'm not sure how well it applies to the current version of V8. [0] http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.h...
- pygy_ 9y agoHello @metrognome, three of your last four posts were [dead] even though none of them appear to break the HN guidelines... I vouched for the last two, the other one is too old for me as user to do anything about it. You may want to contact the mods about this.
- Groxx 9y agoI have been wondering for a while now if there's a terse / "embed some essentially-C code" style asm.js tool. Being able to drop into an optimizer for math / large data purposes, without going full emscripten, seems like it could be extremely useful. That said - have you tried using emscripten on a C version of your code? Any better / worse?
- Vinnl 9y agoDo these same optimisations work well on non-V8 browsers as well, or have we completely given up on avoiding the browser monoculture again?