Hacker News new | ask | show | jobs
Interview with the author of React-like Inferno about JavaScript optimisations (survivejs.com)
79 points by expression100 3476 days ago
8 comments

I've spent a lot of time optimizing the startup time of large React codebases. In my experience the size of the React bundle is irrelevant; it will be quickly dwarfed by the product code for any meaningful application. The real problems in startup speed are twofold:

Browsers are stuck optimizing an outdated model - parsing and compiling JavaScript on the critical path is madness. The only benefit of that model is that a web developer can right click and view source to see some unreadable minified JavaScript code. The cost is that every web user has to wait hundreds or milliseconds or multiple seconds for the browser to do busy work. Even advancements like Service Workers don't do anything to mitigate this problem.

The second problem is that browser vendors are locked in a arms race chasing artificial metrics. This started years ago when the Chrome team introduced V8 with a huge PR push to sell the benefits of their JIT. It looks awesome to say that your browser tops SunSpider but those metrics don't necessarily correlate to how sites use JavaScript. Firefox has a heavily optimized JIT that kicks in when a function is run 1,000 times. When loading Facebook the hottest codepath is run 900 times.

Note: I'm the author of Inferno

I commonly hear the same thing – you've benchmarked React and it performs fine. Did you benchmark this on a modern MacBook/laptop by any chance?

The realism is that the desktop market is only 15% of the entire global online market. In developing nations, Android mobile performance is essential. React, Angular, Ember and other libraries have never performed well in this space.

If you're shipping a huge bundle for your app – you're doing something wrong too. You need to take into account that mobile users are likely to have a 3G connection at best and are likely to be on a 5+ year old device. You need to endorse code-splitting, service workers and other PWA features to better accommodate those users. Inferno will deliver the best in class mobile performance when paired with those features.

It's probably not the same people making these comments, but it's funny to juxtapose "these web technologies are plenty fast, and size is no issue" and "I need 32GB ram + 4 real cores on my macbook for serious web dev".

If the whole system is dedicated to a single page, sure a few hundred KiB of minified javascript is absolutely fine. But neither the developer's system or the user's system is dedicated to a single page. It adds up. It tends to keep adding up until some significant percentage of people hit the limit where stuff is slower than makes sense, considering the AAA games I was playing on a laptop 13 years ago with literally less than 10% of what common laptops today have.

Anyway, kudos for caring about efficiency.

I worked on JavaScript performance at Facebook. I completely agree that we need the things that you mentioned. The problem is that no one is building them.

Instead we get demo apps and poorly thought out blog posts that compare library bundle sizes. It's a distraction that doesn't address the problem and diverts attention from the real problems with web performance.

For almost any real world application the fixed overhead of React/Inferno/Preact will be quickly dwarfed by the overhead of application code, even with code splitting. Unless you can address that cost then you shouldn't be building a client rendered mobile app.

Please, please, write more, and more details!

To much web developers have that "works on my (fastest possible) desktop or notebook" attitude, and every developer that understands and talks about what's actually going on by most of the users (those using the slower devices) is worth gold.

The interview actually talks quite a bit about how I went about making these changes. Worth fully reading!
Thanks. I've read it, what I'd like to see is more of the stuff addressed to other developers, exactly like in your response above.

You understand the issues because you personally compared, measured, whereas most of the developers just consider something as "working" if they personally "don't wait" too much for the stuff they are producing. That's why I wanted to motivate you to consider talking about your experiences that more developers can directly use.

I love your work with Inferno, congratulations.

Is there any place that the community usually hangs out like a chat room or a forum ?

We have a Slack if you're interested: https://inferno-slack.herokuapp.com/
What are example real world websites using Inferno?
>Browsers are stuck optimizing an outdated model

Well, you can always switch to Java applets, Flash or Silverlight. Oh, wait...

>parsing and compiling JavaScript on the critical path is madness.

You know what's madness? Pushing so much client-side code that it destroys your website's performance - despite giant leaps in capabilities of CSS, expressiveness of the language itself, faster CPUs, and tons of engine optimizations.

>The only benefit of that model is that a web developer can right click and view source to see some unreadable minified JavaScript code.

No, the main benefit of that model is that the Web consists of pages, rather than opaque executable blobs.

> You know what's madness? Pushing so much client-side code that it destroys your website's performance - despite giant leaps in capabilities of CSS, expressiveness of the language itself, faster CPUs, and tons of engine optimizations.

And if I can't create something that fits well into that model then what should I do? Create a native app on a proprietary system instead? Change my product to fit the stunted capabilities of the platform?

Front end engineers have internalized the idea that execution is slow, and that the only way we can create performant applications is to defer control to the browser. This is not true for any other platform -- it's merely an artifact of legacy design choices that we continue to pay for today.

>And if I can't create something that fits well into that model then what should I do? Create a native app on a proprietary system instead?

Use Java, of course! I heard you write it once and it runs everywhere. And it doesn't rely on that horrible, outdated Web model. Compiles to binary blobs, just the way you like it.

The benchmark thing is pretty common in GPU vendors as well. It was pretty widely documented a while back that certain vendors were removing thermal limits[1] when certain Android benchmark apps were detected.

Only real solution is to write your own private benchmarks that match your use case and aren't shared widely but not a lot of people have that luxury.

[1] http://www.anandtech.com/show/7384/state-of-cheating-in-andr...

Sadly, this article has nothing to do with an inferno OS port to js.

http://www.vitanuova.com/inferno/

My interpretation of the title was that it was going to discuss how the Dis VM[1] compared to popular JS VMs, but I was sorely disappointed.

(Inferno, for those not aware of it, is a rather obscure successor to the almost-as-obsure Plan 9 From Bell Labs. Plan 9, in turn, was supposed to be the replacement for UNIX. Unfortunately it failed because UNIX was "an existing codebase that is just good enough"[2]; its main lasting contribution was the invention of UTF-8[3].)

[1] http://www.vitanuova.com/inferno/papers/dis.html

[2] Plan 9: The Way the Future Was http://www.faqs.org/docs/artu/plan9.html

[3] http://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt

I'm sorry, this topic has nothing to do with the operating system. Inferno in this context is a popular JavaScript library for building UIs, not an operating system.
Yes, I figured that out as soon as I started reading the article. :) I just figured I'd give some context to thread readers who'd never heard of the OS and were wondering what we were going on about.
It would be amazing to have acme on a browser :)
Agreed and similarly disappointed. Can we please change the title to point out that this is specific to ReactJS?
This has nothing to do with React. This is a JavaScript library called Inferno – the title makes sense to people in the JavaScript scene.
This isn't about ReactJS.
OK, we've attempted to do that and still be accurate.
You'll have to excuse my ignorance as I just like to lightly follow the JS web dev community and am no way a JS expert, but what is the reasoning behind the React spin-offs? There's Inferno, Preact, ivi, and possibly more, but are all of these so different that they require their own repository? Is it out of the question to simply contribute to the other existing open source projects? I imagine there are some backwards compatible breaking changes but it feels a bit weird to have so many React-like spinoffs. Could anyone shed a light on this? I'm genuinely curious.
Note: I'm the author of Inferno and know the authors of Preact and Ivi well.

That's a great question and one I frequently get asked. The "current" React codebase is legacy in many ways, the React team are hard at work on React Fiber which is complete re-write of the entire codebase.

The legacy codebase isn't something we can actively make better (I've had my PRs merged though) without a huge rewrite. If we tried to make changes, it would break React for many people due to how coupled many things are.

A lot has changed in the last few years and the approach to virtual DOM has been one of them. Going forward, the React team are making big efforts to change this.

Certain optimizations that Inferno can do, like avoiding prototype objects, are pretty closely tied to React's API. Are these bottlenecks, or could React (with Fiber and the planned 2017H1 optimizations [0]) eventually become comparable to Inferno's performance for all but the most complex applications?

[0] https://github.com/reactjs/core-notes/blob/master/2016-12/de...

There's no reason why React can't learn from Inferno. In fact, I'm aware that they're using a lot of the internals of Inferno to improve React Fiber. I'm sure we'll see more on this in the future :)
I haven't kept up with React like I should. When you say they're undergoing a re-write, is this going to be like Angular 1 vs Angular 2? I just took a quick look at their react fiber demo page [0], is it going to be opinionated react as redux based? That's the vibe I'm getting from this.

[0] http://reactbits.github.io/fiber/

Fiber is simply a fully-backwards-compatible performance enhancement, though that does it a huge disservice. It's actually figuring out how to run different parts of a render() function in different clock ticks, essentially making renders asynchronous so that animations don't jitter or drop frames, and different parts of the render tree can be prioritized out of order. Unless you were doing weird stuff with low-level animations or React internals, user code won't need to change, but it will instantly become buttery smooth. See https://github.com/acdlite/react-fiber-architecture for how.

Now, the preference for stateless functional components that has been popularized for user code by Redux is certainly a trend, and a focus of performance attention. But that's nothing new.

Unlike Angular 1 -> Angular 2, React Fiber will be fully backwards compatible with the current React.
I probably wouldn't claim 100% compatibility but we're already passing ~95% of the tests, and intend to fix more. There will likely be a few minor differences but we also have 20 thousand components that need to "just work" so we're striving for as much compat as possible, and any changes would be very contained.
You can think of it much more like putting a more advanced engine & drivetrain in a classic race car.

Practically speaking for an end user not a lot will change, but React will be faster, non-blocking, and have a whole new set of applications.

React as an API is brilliant. A Couple of methods only. The libraries conform to the API and achieve different things. Preact is react but simple. It's very lightweight, readable and gets the job done. Inferno is heavily optimized at the cost of readability. React is the original project which is now a monolith. Smaller than angular but still big.

Remember on average every 1Mb of JS Will take about a second to parse and initialize. Smaller, we'll architected libraries like preact and mithriill means you can make pages load within a blink of an eye.

Inferno is 8kb, it's smaller than the Mithril re-write and is on par with Preact in terms of parse performance. I think you may have looked at the older Inferno codebase, as the current one is very slim and readable.
Size isn't everything ;-)

A smaller footprint _does_ usually signify a faster load time, but that can easily be overruled by how its internals are parsed (aka, interpreted by the browser).

For example, 1kb script can intentionally block the mainframe for 10seconds, thus making it slower than a 40kb moderate-performing competitor.

Inferno is optimized for the entire performance profile. Preact may load & parse faster -- but only by a HAIR (10-50ms). Inferno does everything else much faster.

So, do you consider the `load` event (which will occur once in the UX) to be more valuable than the every other interaction?

Inferno only works super fast if you use the babel transpiler that creates the blueprint calls. While this is a great idea, debugging it is a nightmare at times.

I'd rather have simple h or createElement calls. I personally prefer Typescript jsx as all my Components are type validate and refactoring is a breeze.

What I like about preact is its simplicity. I can debug preact and figure out what's going on. Inferno on the other hand seems a bit like assembly. Reusing property names across different contexts is a no-no in my book.

I once worked on a database with generic extra_1 extra_2,... fields. They were abused pretty bad and only one guy knew what they meant in what context.

While trueadm is a perf wizard I would definitely love to see some of the opts being done at the V8/chakra level rather than obfuscated js code.

This isn't true anymore. Blueprints were removed from Inferno quite some time ago. Inferno uses createVNode calls, which are very much like createElement calls.
React is multi platform whereas most of the derivatives target only the browser. This means that whereas React includes event delegation Preact and Inferno can just use the browser's event delegation. As one example of why you might create a spinoff.
I misunderstood the title and was very confused as to why Dan Brown would have things to say about Javascript perf.
React's event system is one of its strong points. Like with many alternatives, I stopped looking into them when i read "no event system" in the README. Perf is not the single most high priority in a framework.
Inferno also has its own event system like React does.
I love it. When it comes to PCs I am a speed junkie. Maybe because the first thing I started to learn as a kid was 3D graphics dev.

Anyway most websites are incredibly slow and clunky. With 20+ scripts and everything pops up or moves while loading.

On the speed of pure JS. I recently developed a JS game just for fun. Optimizing in a language with GC sucks.

the big motivation for small size seems to be shaving the time spent on parsing the js. Is there any reason this can't be parceled off to a background worker, and using the main thread only for rendering?
Pro tip: function calls (especially anonymous ones) are the biggest performance bottleneck. I've done pretty extensive research on this: http://youtu.be/BEqH-oZ4UXI .
So I watched that and heard that you used BenchmarkJS which I believe intentionally prevents optimization from occurring, otherwise many tests would just end up being optimized away. Have you considered how function inlining might affect your results versus benchmarks like you ran in the video? Have you profiled them side by side under actual usage?

"Anonymous" function calls (function expressions, either immediate or not) are generally only more expensive because they're re-created all the time. Sometimes this is necessary to create a closure, often it's not. If you were to profile `var foo = function() {}`, performance should match the function declaration variant identically, so it's not the method of declaration that affects performance.

That is something I have been suspecting of BenchmarkJS, which is unfortunate because I am/wasn't expecting to be responsible for building a better benchmarking tool (we are already building a database and a distributed testing framework).

FireFox does inline, and we can tell even with BenchmarkJS, which is why I think V8 wasn't (although it has improved significantly recently).

We ran an "actual use" test that saved 100M+ records a day for $10 total (on about 100GB+, processing, storage, backup). However since it is an "actual use" we don't know if inlining happened or not (I still suspect not, since I haven't seen that in V8 - but again, maybe that is BenchmarkJS killing it, despite seeing it in FF).

Correct correct. The function call is still expensive (anonymous or not), and managing scope is extremely expensive. You are on top of this stuff! Sounds like you do performance testing regularly?

I do, but it's also easy to sound smart when you're standing on the shoulders of very smart people. @jdalton and @petkaantonov are two that I've regularly followed and read their code for learning purposes. IRHydra by @mraleph is a great tool for learning about V8 internals and examining what's going on under the hood. Bluebird is an amazing example to follow if you really want to optimize, however the vast majority of libraries and apps don't need to concern themselves with some of the crazier optimizations that are performed there.

The biggest takeaways that I've had are mainly that if you really want to optimize, then write the kind of code that the various engines can optimize best. With that in mind, TypeScript is not only a great language, it's actually a great performance tool since it can help you to identify polymorphic functions that maybe could be changed to monomorphic ones if they're in the hot path.

I've got another fun hacky kind of optimization for you though that I've mainly used for benefit in some angular code/expressions that are executed potentially millions of times. If you can make some assumptions about the name of the property being accessed for security reasons and it's going to be repeatedly used, compare the following:

// Setup

const makeGetter = (prop) => new Function("obj", "return obj." + prop");

const slowGetter = (obj, prop) => obj[prop];

const fastGetter = makeGetter("foo");

const obj = { foo: "bar" };

// Test

slowGetter(obj, 'foo');

fastGetter(obj);

This is primarily an optimization for Internet Explorer, but if I recall it was somewhat positive in other browsers at the time as well. It makes more sense if you consider it in the context of executing a simple angular expression repeatedly, though 1.6.0 might narrow that gap significantly now that the expressions sandbox was removed as I think they generate code similar to my "makeGetter" factory. Haven't re-tested yet.

Is there a written summary of this anywhere?
I tried to give a summary in the video description (click the view more). I hope that helps.
Thank you, missed that