Archive for December, 2021

2021 book review

I’ve been doing end-of-the year book reviews for almost 5 years now. At this point I have to ask myself: why am I still doing this?

To encourage myself to read more? To show off? To convince myself that this blog is about more than just tech stuff? There may be some truth to all those, but I think my main goal is just to recommend some good books to others. I don’t use GoodReads (although I link to it, as it seems nice), so this is my forum where I highlight books I’ve enjoyed, in the hope that others might find something interesting to read in the new year.

So without further ado, on with the book reviews!

Quick links

Fiction

Non-fiction

Fiction

Like last year, I’ve been reading a lot of fantasy novels. My methodology is crude: I just googled “best fantasy novels” and started from there. In the past, I was never much of a wizards-and-pegasuses kind of reader (I always preferred sci-fi and dystopias), so I’m trying to make up for lost time.

The Name of the Wind and The Wise Man’s Fear by Patrick Rothfuss

I struggled to like the first book. My main beef was that 1) it’s a bit too predictable and groan-inducing with how the main character is a preternaturally gifted Mary Sue who just inevitably excels at everything, and 2) after a great “street urchin” backstory, the action really grinds to a halt when the character arrives at university and mostly mopes after his would-be girlfriend.

The second book, however, redeems the first one in my eyes. It makes up for some of the dull campiness of the first book with a never-ending series of inventive subplots. Just as soon as you’re bored with one setting or cast of characters, it dramatically switches to another. It almost feels like a collection of vignettes.

I’m eagerly awaiting the third book, which (like The Winds of Winter by George R. R. Martin), seems perpetually delayed.

Dragonflight by Anne McCaffrey

This is another book that I struggled to like. The premise is so good (dragons! extra-planetary colonization! a perennial existential threat!), and I have friends who rave about the Dragonriders of Pern series. But to be honest, I just found it to be a bit of a slog. I felt like the author was taking too much time to set up names, places, history, concepts – almost like she started writing an encyclopedic Silmarillion rather than an accessible Hobbit. By the time I had gotten the lingo down and could keep the characters’ names straight, the story was over.

I’ve picked up the next couple books in the series, and I’m going to give them a shot, but I don’t have high hopes.

Kindred by Octavia Butler

What I love about this book is that the author takes a completely ridiculous premise and treats it with utmost seriousness, and by the end you’re so invested in the story that it doesn’t matter that the paranormal elements are never explained. Gives you a good sense of what it would feel like to live in a society where daily barbarism is completely normalized.

Is it sci-fi? Is it fantasy? Hard to categorize, but I would lean towards sci-fi, if we define sci-fi as “putting human beings in otherworldly situations to see how they tick.” In any case, a great read.

2034 by Elliot Ackerman and James Stavridis

A chilling and all-too-plausible near-future sci-fi. I appreciate the attention to detail that comes from having a subject matter expert (in military matters) as a co-author. Hopefully it will turn out to be a cautionary tale rather than a prescient prediction.

The Ministry for the Future by Kim Stanley Robinson

A book that starts out with a bang and gradually limps towards an ending. I had to put it down ~80% of the way through because it got into preachy, starry-eyed utopia territory. Maybe I’m just a cynic, but the more pessimistic predictions in the book seem way more believable to me.

Premier Sang by Amélie Nothomb

Amélie Nothomb is one of my favorite Francophone authors, and not just because my French is terrible, and her writing is simple enough that I can understand it without constantly switching to a dictionary. I picked up this book at random while on vacation in France and gobbled it up on the plane ride back.

The story starts out with an incredible hook – a firing squad! – and from there gives a richly detailed (and ultimately personal) character study. The scenes from the protagonist’s childhood, where he’s alternately coddled and neglected (but craves the latter!), are especially poignant.

Sorry for recommending a non-English book, but hopefully it will be translated soon!

Non-fiction

Why We Love Dogs, Eat Pigs, and Wear Cows by Melanie Joy

I’ve spent probably the past 15 years of my life struggling with a basic question: what to eat? I’ve gone through carnism, pescetarianism, vegetarianism, veganism, and right back around several times. These days I’m probably best-described as flexitarian (i.e. I avoid meat, but I won’t turn down a turkey dinner at Thanksgiving).

If you’re not already interested in vegetarianism or veganism, this book will not convince you of anything. For myself, I found it pretty depressing, because the situation feels kind of hopeless to me. The sheer scale of animal suffering in factory farms makes it a good candidate for one of, if not the, most consequential ethical questions of our day, and yet the average person couldn’t care less, and is irritated to even consider it. Exploring this question will make you the most unpopular person at a dinner party, and probably cause a lot of stress and annoyance for your friends and relatives if they feel obliged to accommodate your dietary choices.

So why do I read this stuff? Well I guess, like a good car crash, I just can’t look away. If I’m going to be an ethical monster, I would at least like to be cognizant of it when I put a forkful of egg or cheese (or rarely, meat) into my mouth. And I’d like to have a ready-made answer if someone asks why I always order the tofu. And I’d like to steel my resolve as I continually search for good beans-and-rice and tempeh recipes that can compete with my fond memories of a juicy Reuben sandwich. (This stir-fry recipe is quite good.) My inner monologue on food is complicated, I don’t have it all figured out, but I’m trying to wrestle with the tough questions.

Dialogues on Ethical Vegetarianism by Michael Huemer

Another pro-vegan book that will depress you if you’re already converted, and probably convince you of nothing if you’re not. For myself, I found it interesting because it fairly neatly demolishes all of the plausible excuses for eating meat or animal products. (Yes, that one, and that one, and that other one you just thought of.) This is a good book for the open-minded person who really wants to engage with the best arguments for veganism, not just the straw-man.

I’ll also say that, for a philosophy book, this is eminently readable. I really enjoy the short, brisk pace and the “Socratic dialogue” style rather than a long-winded essay format.

How Not to Die by Michael Grieger

As you might have noticed, I kind of went on a tear this year reading vegan literature. I really wanted to confront my meat-eating (and egg-eating, and dairy-eating) head on, so I tried to read all the “greatest hits” of vegan literature.

This book has a lot of sensible advice (eat more whole grains, eat more nuts and berries), although I get the impression that the author is pretty dogmatic in promoting a pure-vegan lifestyle. Based on reviews I’ve read of the book, he tends to ignore any research that advocates for moderate consumption of eggs, cheese, and fish, even though those are (as far as I can tell) pretty good ingredients in a healthy diet.

On the other hand, I do appreciate his no-nonsense, uncompromising position on certain health questions. (Salt? Nope, just avoid it. Oil? Nope, just fry everything with water or vinegar! Exercise? 30 minutes every day!) I prefer the “give it to me straight, doc” approach, rather than a resigned shrug and “Well, if you’re going to drink beer and eat potato chips, at least do it in moderation.” Although I think his advice is much too extreme for the average person to actually adhere to.

Hate, Inc. by Matt Taibbi

One of the best political non-fiction books I’ve read. For a few years, I’ve had the gnawing feeling that something in the media (including social media) felt “off,” but I couldn’t quite put my finger on it. This book does a good job of explaining why our media feels so hyper-partisan, and therefore less trustworthy.

Against the Grain by James C. Scott

Elaborates on one of the minor points you may recall from Sapiens by Yuval Noah Harari about how the agricultural revolution was probably kind of a bum deal for humanity. Also has some interesting commentary on the origins of viruses from livestock, and how they probably played havoc on early civilizations. (This book was written pre-Covid, by the way!)

A Brief History of Everyone Who Ever Lived by Adam Rutherford

A fun and intriguing read. Makes you realize how silly and petty (and temporary!) most of our human squabbles over race and ethnicity are. Also gives a great explanation of why “I descended from Charlemagne” is not such a remarkable statement.

The End of the End of History by Alex Hochuli, George Hoare, and Philip Cunliffe

A good, heterodox leftist perspective on the whole “what the heck is up with liberal democracy?” genre. A good pairing with The New Class War by Michael Lind.

Introducing fuite: a tool for finding memory leaks in web apps

Debugging memory leaks in web apps is hard. The tooling exists, but it’s complicated, cumbersome, and often doesn’t answer the simple question: Why is my app leaking memory?

Because of this, I’d wager that most web developers are not actively monitoring for memory leaks. And of course, if you’re not testing something, it’s easy for bugs to slip through.

When I first started looking into memory leaks, I assumed it was a rare thing. How could JavaScript – a language with an automatic garbage collector – be a big source of memory leaks? But the more I learned, the more I suspected that memory leaks were actually quite common in Single Page Apps (SPAs) – it’s just that nobody is testing for it!

Since most web developers aren’t fiddling with the Chrome memory tools for the fun of it, they probably won’t notice a leak until the browser tab crashes with an Out Of Memory error, or the page slows down, or someone happens to open up the Task Manager and notice that a website is using many megabytes (or even gigabytes!) of memory. But at that point, it’s gotten bad enough that there may be multiple leaks on the same page.

I’ve written about memory leaks in the past, but my advice basically boils down to: “Use the Chrome DevTools, follow these dozen tedious steps, and then maybe you can figure out why your page is leaking.” This is not a great developer experience, and I’m sure many readers just shook their heads in despair and moved on. It would be much better if a tool could find memory leaks automatically.

That’s why I wrote fuite (French for “leak”). fuite is a CLI tool that you can point at any URL, and it will analyze the page for memory leaks:

npx fuite https://example.com

That’s it! By default, it assumes that the site is a client-rendered SPA, and it will crawl the page for internal links (such as /about or /contact). Then, for each link, it runs the following steps:

  1. Click the link
  2. Press the browser back button
  3. Repeat to see if memory grows

If fuite finds any leaks, it will show which objects are suspected of causing the leak:

Test         : Go to /foo and back
Memory change: +10 MB
Leak detected: Yes

Leaking objects:

| Object            | # added | Retained size increase |
| ----------------- | ------- | ---------------------- |
| HTMLIFrameElement | 1       | +10 MB                 |

Leaking event listeners:

| Event        | # added | Nodes  |
| ------------ | ------- | ------ |
| beforeunload | 2       | Window |

Leaking DOM nodes:

DOM size grew by 6 node(s)  

To do this, fuite uses the basic strategy outlined in my blog post. It will launch Chrome, run some scenario n number of times (7 by default) and see if any objects are leaking a multiple of n times (7, 14, 21, etc.).

fuite will also analyze any Arrays, Objects, Maps, Sets, event listeners, and the overall DOM to see if any of those are leaking. For instance, if an Array grows by exactly 7 after 7 iterations, then it’s probably leaking.

Testing real-world websites

Somewhat surprisingly, the “basic” scenario of clicking internal links and pressing the back button is enough to find memory leaks in many SPAs. I tested fuite against the home pages for 10 popular frontend frameworks, and found leaks in all of them:

Site Leak detected Internal links Average growth Max growth
Site 1 yes 8 27.2 kB 43 kB
Site 2 yes 10 50.4 kB 78.9 kB
Site 3 yes 27 98.8 kB 135 kB
Site 4 yes 8 180 kB 212 kB
Site 5 yes 13 266 kB 1.07 MB
Site 6 yes 8 638 kB 1.15 MB
Site 7 yes 7 1.37 MB 2.25 MB
Site 8 yes 15 3.49 MB 4.28 MB
Site 9 yes 43 5.57 MB 7.37 MB
Site 10 yes 16 14.9 MB 186 MB

In this case, “internal links” refers to the number of internal links tested, “average growth” refers to the average memory growth for every link (i.e. clicking it and then pressing the back button), and “max growth” refers to whichever internal link was leaking the most. Note that these numbers don’t include one-time setup costs, as fuite does one preflight iteration before the normal 7 iterations.

To confirm these results yourself, you can use the Chrome DevTools Memory tab. Here is a screenshot of the worst-performing site from my set, where I click a link, press the back button, take a heap snapshot, and repeat:

Screenshot of the Chrome DevTools memory heapsnapshots list, showing memory starting at 18.7MB and increasing by roughly 6MB every iteration until reaching 41 MB on iteration 5

On this particular site, memory grows by about 6 MB every time you click a link and go back.

To avoid naming and shaming, I haven’t listed the actual websites. The point is just to show a representative sample of some popular SPAs – the authors of those websites are free to run fuite themselves and track down these leaks. (Please do!)

Caveats

Note, though, that not every leak in an SPA is an egregious problem that needs to be addressed. SPAs need to, for example, maintain the focus and scroll state to properly support accessibility, which means that there may be some small metadata that is stored for every page navigation. fuite will dutifully report such leaks (because they are leaks), but it’s up to the developer to decide if a tiny leak is worth chasing or not.

Some memory growth may also be due to browser-internal changes (such as JITing), which the web page can’t really control. So the memory growth numbers are an imperfect measure of what you stand to gain by fixing leaks – it could very well be that a few kBs of growth are unavoidable. (Although fuite tries to ignore browser-internal growth, and will only say “leaks detected” if there is actionable advice for the web developer.)

In rare cases, some memory growth may also be due to outright browser bugs. While analyzing the sites above, I actually found one (Site #4) that seems to be suffering from this Chrome bug due to <img loading="lazy"> not being unloaded. Unfortunately it’d be hard for fuite to detect browser bugs, so if you’re mystified by a leak, it’s good to cross-check against other browsers!

Also note that it’s almost impossible for a Multi-Page App (MPA) to leak, because the browser clears memory on every page navigation. (Assuming no browser bugs, of course.) During my testing, I found two frontend frameworks whose home pages were MPAs, and unsurprisingly, fuite couldn’t find any leaks in them. These were excluded from the results above.

Memory leaks are more of a concern for SPAs, where memory isn’t cleared automatically on each navigation. fuite is primarily designed for SPAs, although you can run it on MPAs too.

fuite currently only measures the JavaScript heap memory in the main frame of the page, so cross-origin iframes, Web Workers, and Service Workers are not measured. Something like performance.measureUserAgentSpecificMemory() would be more accurate, but it’s only available in cross-origin isolated contexts, so it’s not practical for a general-purpose tool right now.

Other memory leak scenarios

The “crawl for internal links” scenario is just the default one – you can also build your own. fuite is built on top of Puppeteer, so for whatever scenario you want to test, you essentially just need to write a Puppeteer script to tell the browser what to do. Some common scenarios you might test are:

  • Open a modal dialog and then close it
  • Hover over an element to show a tooltip, then mouse away to dismiss it
  • Scroll through an infinite-loading list, then navigate away and back
  • Etc.

In each of these scenarios, you would expect memory to be the same before and after. But of course, it’s not always so simple with web apps! You may be surprised how many of your dialogs and tooltips are harboring memory leaks.

To analyze leaks, fuite captures heap snapshot files, which you can load in the Chrome DevTools to inspect. It also has a --debug mode that you can use for more fine-grained analysis: stepping through the test as it’s running, debugging the browser in real-time, analyzing the leaking objects, etc.

Under the hood, fuite is a fairly basic tool, and I won’t claim that it can do 100% of the work of fixing memory leaks. There is still the human component of figuring out why your objects were allocated and retained, and then finding a reasonable fix. But my goal is to automate ~95% of the work, so that it actually becomes achievable to fix memory leaks in web apps.

You can find fuite on GitHub. Happy leak hunting!

Update: I made a video tutorial showing how to debug memory leaks with fuite.

One weird trick to improve your website’s performance

Every so often, I come across a web performance post from what I like to call the “one weird trick” genre. It goes something like this:

“I improved my page load time by 50% by adding one line of CSS!”

or

“It’s 2x faster to use this JavaScript API than this other one!”

The thing is, I love a good performance post. I love when someone finds some odd little unexplored corner of browser performance and shines a light on it. It might actually provide some good data that can influence framework authors, library developers, and even browser vendors to improve their performance.

But more often than not, the “one weird trick” genre drives me nuts, because of what’s not included in the post:

  • Did you test on multiple browsers?
  • Did you profile to try to understand why something is slower or faster?
  • Did you publish your benchmark so that others can verify your results?

That’s why I wrote “How to write about web performance”, where I tried to summarize everything that I think makes for a great web perf post. But of course, not everyone reads my blog religiously (how dare they?), so the “one weird trick” genre continues unabated.

Look, I get it. Writing about performance is hard. And we’re not all experts. I’ve made the same mistakes myself, in posts like “High performance web worker messages” (2016) – where I found the “one weird trick” that it’s faster to stringify an object before sending it to a web worker. Of course this makes little sense (the browser should be able to serialize the object faster than you can do it yourself), and Surma has demonstrated that there’s no need to do this stringify dance in modern versions of Chrome. (As I’ve said before: if you’re not wrong about web perf today, you’ll be wrong tomorrow when browsers change!)

That said, I do occasionally find a post that really exemplifies what’s great about the web perf genre. For instance, this post by Eoin Hennessy about improving Webpack performance really ticks all the boxes. The author wasn’t satisfied with finding “one weird trick” – they had to understand why the trick worked. So they actually went to the trouble of building Node from source (!) to find the true root cause, and they even submitted a patch to Webpack to fix it.

A post like this, like a good mystery novel, has everything that makes for a satisfying story: the problem, the search, the resolution, the ending. Unlike the “one weird trick” posts, this one doesn’t leave me craving more. Instead, it leaves me feeling like I truly learned something about how browser engines work.

So if you’ve found “one weird trick,” that’s great! There might actually be something really interesting there. But unless you do the extra research, it’s hard to say more than just “Well, this technique worked for me, on my website, in Chrome, in this scenario…” (etc.). If you want to extrapolate from your results to something more widely-applicable, you have to put in the work.

So here are some things you can do. Test in multiple browsers. File a browser bug if one is slower than the others. Ask around if you know any web perf experts or folks who work at browser vendors. Take a performance profile. And if you put in just a bit of extra effort, you might find more than “one weird trick” – you might find a valuable learning opportunity for web developers, browser vendors, or anyone interested in how the web works.