How we improved our app’s performance by shipping the correct builds for arm

  1. requestly
  2. requestly
  3. arm
  4. mac

Let’s get one thing out of the way, most of the people who know Requestly don’t really use the desktop app. So for all you soon-to-be-mesmerized-debuggers our desktop app is even more amazing! Go check it out.

And if you have any problems. Feel free to reach out to me on twitter

The time when our app was very, very slow….

We were always aware that our app runs somewhat slow for some of our mac users, but never really paid attention to it. At first glance it might seem like ignorance, but really wasn’t that obvious because:

  1. most of our users use the web app
  2. our internal team were all testing on the same kinds of machines, so collectively we all had accepted a slow app as being the norm, for now

The critical functionality was working well for most of our users, but it was this error that was shared in our internal slack that started to raise questions

Error asking to install rosetta when trying to install Requestly
Error asking to install rosetta when trying to install Requestly

before that whenever someone asked me about why we don’t ship rosetta I would always frame an answer like:

My naive reply whenever someone asked "Why don't we have two different download options for macOS"
My naive reply whenever someone asked "Why don't we have two different download options for macOS"

I was too naive and honestly, just not that experienced in electron. Should had known better.

But before we know what was happening, let's see the improvements that are delayed because of my naive reply.

Benchmarks

We tested out the app for four different cases:

  1. Launching x64 builds on rosetta after a fresh install
  2. Launching x64 builds using the app for sometime and then closing it
  3. arm builds after fresh install
  4. arm builds after some usage

I was too lazy to start adding timers to the code for these benchmarks. So all these have been manually measured a few times, so numbers might be a little off

But one thing is clear, there is a HUGE improvement on the app launch time.

from more than 20 sec → less than 2 sec

Time to app becoming usable
Time to app becoming usable

We also measured the time the inital processes took to complete. These include:

  1. The splash screen
  2. Starting the proxy
  3. Detecting the browsers

Since most of the time went into loading the app, here’s a graph that shows the time of all these processes except the initial launch time.

Time taken for all critical processes
Time taken for all critical processes

See how there’s nothing there for the arm build after the initial launch. That’s because everything is now taken care of at launch time. The app is literally ready to use when it has launched.

What role rosetta was playing here

I used to assume that electron took care of everything always. Even setting up a build pipeline isn’t that hard to figure out when you are using awesome packages like electron-builder

But we made a blunder that we didn’t notice until it was too late.

For macOS, for which we shipped as a .dmg, we were only shipping the x64 version.

For those of you that have used an m1 or m2 mac, you might have heard of Rosetta. It is a well know apple “Hack” to make apps made for the intel macs work on the arm chips.

And all this time I though electron was doing all the magic, and I only needed to worry if I was compiling and attaching any additional binaries myself.

I trusted electron too much.

You see, when you package an electron app, it also ships a JIT (Just in time compiler) to run the nodejs code that we add in the main process.

Rosetta also has it’s own JIT that it possible to run intel applications on an arm device. The problem then becomes that if an electron app is run with the help of Rosetta you then have a JIT running the code of another JIT which is eventually trying to run your code

How electron app is run with the help of Rosetta
How electron app is run with the help of Rosetta

should had just once seen this post for the right context - https://www.electronjs.org/blog/apple-silicon

So how did we solve it

Building for arm

Compiling for arm is easy, even more so when you already use arm.

I was just skeptical about how this would affect the upgrade process. We use the electron-updater package to update our code, but that is code is such a legacy part of the application, albeit very critical, that we are very careful on what changes we make there.

We have this warning there just in case we accidentally wander into that part of the code and start making changes

This was inspired by something I similar I saw in Godot's source code
This was inspired by something I similar I saw in Godot's source code

But, to our surprise electron-updater took care of every case gracefully. It does so by updating the yaml file describing the next release. And while checking for the update it also verifies if a build is present user’s current host type. We still tested it out and it worked like a charm

Seeing all this happen all on it’s own, brought back my love for electron ❤️‍🩹

The only decision we had to make was how to distribute these.

On macos we could either go for universal builds. These shipped both the intel and arm builds in one .dmg and let the host machine decide which one to install

The other option was to ship two separate two separate builds for intel and arm based macs. This would not only reduce the build size, it also let the user decide which build they needed. And with electron-update doing all the heavy lifting of updating users to the right builds, it was a no brainer to choose this option.

The only downside was that the builds now took almost double the time because arm build assets also needed to be notarized.


I hope the desktop app is now blazingly fast for you. But that’s not the end of it. We are constantly trying improve the app so that you can debug your websites faster.

So any and all feedback are always welcome.

Happy debugging.