docs.rodeo

MDN Web Docs mirror

Optimizing startup performance

{{QuickLinksWithSubPages("Web/Performance")}} 

Improving your startup performance is often one of the highest value performance optimizations that can be made. How long does your app take to start up? Does it seem to lock up the device or the user’s browser while the app loads? That makes users worry that your application has crashed, or that something else is wrong. Good user experience includes ensuring your app loads quickly. This article provides performance tips and suggestions for both writing new applications and porting applications to the web from other platforms.

Fast asynchronous loading

Regardless of platform, it’s always a good idea to start up as quickly as possible. Since that’s a universal issue, we won’t be focusing on it too much here. Instead, we’re going to look at a more important issue when building Web apps: starting up as asynchronously as possible. That means not running all your startup code in a single event handler on the app’s main thread.

Rather, create a Web worker that does as much as possible in a background thread (for example, fetching and processing data.) Relegating tasks to a Web worker frees up the main thread for tasks requiring it, like user events and rendering UI. In turn, main thread events should consist of many small tasks, also known as micro tasks, rather than larger, more time consuming tasks.

Asynchronous loading helps prevent pages and user interfaces from appearing to be (or actually becoming) unresponsive. By minimizing the time required for any individual loading task, the application’s event loop will continue to cycle while it starts up. This will prevent the application, browser, and/or device from appearing frozen.

In the worst case, blocking the main thread can cause users to uninstall your app; for example, if someone launches your app by mistake and they aren’t prevented from closing the application, they may want to take action so that doesn’t accidentally happen again.

Where there’s a will…

It is easier to just write everything the “right way” the first time then it is to retrofit for performance (and accessibility). When you are starting from scratch, making appropriate bits of code asynchronous means a retrofit isn’t necessary. All pure startup calculations should be performed in background threads, while you keep the run-time of main thread events as short as possible. Instead of including a progress indicator so the user knows what’s going on and how long they’ll be waiting, make the progress bar unnecessary.

On the other hand, porting an existing app to the Web can be challenging. Native application don’t need to be written in an asynchronous fashion because the operating system usually handles loading for you. The source application might have a main loop that can easily be made to operate asynchronously (by running each main loop iteration separately); startup is often just a continuous, monolithic procedure that might periodically update a progress meter.

While you can use Web workers to run even very large, long-duration chunks of JavaScript code asynchronously, there’s a huge caveat: Web workers can’t directly manipulate the DOM and have limited access to methods and properties of the window object, including no access to WebGL. This all means that unless you can easily pull out the “pure calculation” chunks of your startup process into workers, you’ll wind up having to run most or all of your startup code on the main thread.

However, even code like that can be made asynchronous, with a little work.

Getting asynchronous

Here are some suggestions for how to build your startup process to be as asynchronous as possible (whether it’s a new app or a port):

The more stuff you can do asynchronously, the better advantage your app can take of multicore processors.

Porting issues

Once the initial loading is done and the app’s main code starts to run, it’s possible your app has to be single-threaded, especially if it’s a port. The most important thing to do to try to help with the main code’s startup process is to refactor the code into small pieces. These can then be executed in chunks interspersed across multiple calls to your app’s main loop (so that the main thread gets to handle input and the like).

Emscripten provides an API to help with this refactoring; for example, you can use emscripten_push_main_loop_blocker() to establish a function to be executed before the main thread is allowed to continue. By establishing a queue of functions to be called in sequence, you can more easily manage running bits of code without blocking the main thread.

That leaves, though, the problem of having to refactor your existing code to actually work that way. That can take some time.

How asynchronous should I get?

The faster your site first becomes usable and the more responsive it is to user input, the better it will be perceived. A site that takes 1 or 2 seconds before content first appears is usually seen as fast; if you’re used to sites taking 3 or 4 seconds, then 7 or 8 seconds feels like a very long time.

In terms of responsiveness, users won’t notice a delay of 50ms or less. Any delay of over 200ms and the user will perceive your site as sluggish. When working to improve the loading and responsiveness of your applications, remember that many of your users may have older, slower computer than yours, they may experience longer delays than you do!

Other suggestions

There are other things beyond going asynchronous, which can help you improve your app’s startup time. Here are a few of them:

See also

In this article

View on MDN