Skip to content

Instantly share code, notes, and snippets.

@nolanlawson
Last active August 14, 2023 20:08
Show Gist options
  • Save nolanlawson/e73c61da78ffb39e4fc034a62ce8b263 to your computer and use it in GitHub Desktop.
Save nolanlawson/e73c61da78ffb39e4fc034a62ce8b263 to your computer and use it in GitHub Desktop.
"Parens and Performance" – counterpost

"Parens and Performance" – counterpost

Kyle Simpson (@getify) wrote a very thoughtful post decrying optimize-js, which is a tool I wrote that exploits known optimizations in JavaScript engines to make JS bundles parse faster (especially minified bundles, due to what could be reasonably described as a bug in Uglify).

Kyle lays out a good case, but I tend to disagree with nearly all his points. So here's my rebuttal.

Outsmarting the browser

Yay, right!?!? Right? Look how clever we are, outsmarting the browser at its own game. Humans 1, Computers 0.

Yes, we absolutely can outsmart the browser. optimize-js barely scratches the surface of the optimizations you can make, once you realize that:

  1. We have the full AST of the JS bundle when we're building/minifying it.
  2. The browser doesn't have any of this info in advance; its parsing optimizations are mostly greedy.
  3. We can take advantage of this asymmetry of information between us and the browser to provide hints to it.

Right now the optimize-js algorithm is pretty simple: paren-wrap all true IIFEs (whatever their form), and also wrap functions-passed-to-functions (because this catches module definitions like AMD/UMD, Browserify, etc.). However you could imagine further optimizations, such as:

  1. Functions passed to .define() and .then() get wrapped, functions passed to .addEventListener() and .on() don't.
  2. Or a more sophisticated approach: functions that the compiler can conclusively determine to be immediately executed get wrapped, others don't.
  3. Or in the case of module bundlers: if we know the whole module graph, we can determine which ones are lazy-require()d and which ones aren't, and paren-wrap accordingly
  4. Or we could get even fancier with IIFE detection. For instance, this:
function foo() {
  var yolo = 'yolo';
  function bar() {
    console.log(yolo);
  }
  bar();
}

could become this:

function foo() {
  var yolo = 'yolo';
  (function bar() {
    console.log(yolo);
  })();
}

The possibilities go on and on, once you realize you've got the full AST to work with, and all the time in the world to apply transformations to it.

So sure, in its current implementation, optimize-js is gonna have a few false positives. But that's not an argument against the approach itself; it's an argument to improve it.

Cat and mouse

Over time, JS engines will start to realize that most code is being parsed immediately, and not much JITing is happening. They'll realize the heuristics need to change, to get back to a better balance.

This has happened since time immemorial. For instance, Chakra is already optimizing for weirdo Uglify patterns because that's what we see out there in the wild.

If Uglify were to remove the !function(){}()-style IIFEs, it would immediately negate the hard-won optimizations Chakra has implemented for Uglify (namely to check for ! before function) and Chakra would have to try to find new optimizations. It's a cat-and-mouse game.

Corrupting the youth

So when a dev sees that they have a function declaration like function foo() { .. }, they'll realize that they can't just wrap ( ) around it to "just make it run faster". So they'll refactor them to let foo = (function foo() { .. }); style,

This is a major plank of Kyle's argument against optimize-js, and it's the part I find least persuasive. I'll make my case by way of analogy:

Ugh, all these JavaScript minifiers are teaching developers the wrong patterns! Soon they'll start naming all their variables e and t, because they have some vague notion it's "faster."

Or:

Gzip is a blight on our industry! Developers are going to start thinking it's okay to repeat the same boilerplate code over and over again, because hey, "gzip will take care of it, right?"

I never intended optimize-js as anything other than a compiler step, and in fact my hope is that it can provide optimizations that folks wouldn't be able to implement themselves (e.g. the lazy-module example above). We've been optimizing JavaScript for browser delivery since we first started minifying/gzipping; I see optimize-js as part of the same time-honored tradition.

As for Kyle's arguments about !important and translateZ(), I find that comparison pretty strained because, again, I consider optimize-js a compiler step, not an authoring concern. If we had had better compilers back in the day, then arguably we could have done the "cache the length of an array in a for-loop" sort of stuff in a compiler as well, where it's easier to remove once engines improve and the optimization is no longer needed.

It's just a flesh wound

Stop doing silly stuff for the sake of saving a few milliseconds.

I work on the performance team for Microsoft Edge. A big part of my job is meeting with webdev teams (internal and external) and analyzing their website to determine why it's running so slow.

100% of the time (and that is not an exaggeration) their biggest problem with performance is that they are running too much JavaScript on initial load. This is the thing that's absolutely killing performance on the web these days, and especially the mobile web: we're shipping megabytes of JavaScript on initial page load, slowing down the time-to-first-interaction (and often time-to-first-paint sadly) and making the overall experience of the web suck.

Sam Sacconne talking about apps that take a poo on the main thread for 20 seconds

I wish Sam were exaggerating about the "20 seconds" thing. I've seen even worse.

There are lots of potential solutions to this problem (code splitting, PRPL, not writing so much damn JavaScript), and I don't claim any one of them to be the silver bullet. But if I can get Ember to load in 1 millisecond instead of 70 (on desktop!) or an 8MB JavaScript file to parse 30% faster, then that's not money I'm going to leave on the table.

The web needs to be fast, it needs to be fast now, and I'm going to use whatever dirty tricks I have at my disposal to make that happen.

@benlesh
Copy link

benlesh commented Sep 20, 2016

Well written with limited poo-slinging.

I agree with all of this, but I'd like to add that the whole "stop trying to save a few milliseconds" thing is such a misnomer. It's maybe a few milliseconds on a development laptop that's not running anything else... But on a busy machine with lots of chrome extensions, or an under-powered web device, those milliseconds become seconds quickly.

@ezekielchentnik
Copy link

ezekielchentnik commented Sep 25, 2016

Stop doing silly stuff for the sake of saving a few milliseconds.

Large numbers are the accumulation of small numbers

@pleath
Copy link

pleath commented Sep 29, 2016

Thanks, Nolan. I agree with your points in principle, though I think the phrase "outsmart the browser" (not your coinage, I realize) is misleading, when what you're really doing is "making the browser smarter by telling it what you know about your program."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment