Revision 380479 of Optimizing startup performance

  • Revision slug: Apps/Developing/Optimizing_startup_performance
  • Revision title: Optimizing startup performance
  • Revision id: 380479
  • Created:
  • Creator: Sheppy
  • Is current revision? No
  • Comment

Revision Content

An often overlooked aspect of app software development—even among those focusing on performance optimization—is startup performance. 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 app is crashed, or that something else is wrong. It's always a good idea to take the time to ensure that your app starts up nicely. This article offers tips and suggestions to help you achieve that goal, both when writing a new app and when porting an app from another platform to the Web.

Starting up nicely

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.

Instead, you should write your code so that your app creates a Web worker that does as much as possible in a background thread. Then, anything that must be done on the main thread should be broken up into small pieces so that the app's event loop continues to cycle while it starts up. This will prevent the app, browser, and/or device from appearing to have locked up.

Why is it important to be asynchronous? Other than the reasons suggested above, consider the impact of a non-responsive page or user interface. The user is unable to cancel if they launched your app by mistake. If the app is being run in a browser, it's possible the user may get an "unresponsive app" or "slow script" notification. You should present some kind of interface, such as a progress bar, so that the user knows how much longer they'll need to wait while your app starts up.

Where there's a will...

If you're starting your project from scratch, it's usually pretty easy to just write everything the "right way," making appropriate bits of the code asynchronous. All pure startup calculations should be performed in background threads, while you keep the run-time of main thread events as short as possible. Include a progress indicator so the user knows what's going on and how long they'll be waiting. In theory, anyway, it should be pretty easy to design your new app to start up nicely.

On the other hand, however, it can get tricky when you're porting an existing app to the Web. A desktop application doesn't need to be written in an asynchronous fashion because usually the operating system handles that for you, or the app is the only thing that matters which is currently running, depending on the operating environment. 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 that: workers don't have access to WebGL or audio, and they can't send synchronous messages to the main thread, so you can't even proxy those APIs to the main thread. 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):

  • If you need to decode asset files (for example, decoding JPEG files and turning them into raw texture data for later use by WebGL), that's great to do in workers.
  • When dealing with data supported by the browser (for example, decoding image data), use the decoders built into the browser or device rather than rolling your own or using one from the original codebase. The provided one is almost certainly significantly faster, and will reduce your app size to boot. In addition, the browser may automatically parallelize these decoders.
  • Any data processing that can be done in parallel should be. Don't do one chunk of data after another; do them all at once when possible!

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

Once the initial loading is done and the app's main code starts to run, it's possible your app may necessarily 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 that can be done 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.

Revision Source

<p>An often overlooked aspect of app software development—even among those focusing on performance optimization—is startup performance. 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 app is crashed, or that something else is wrong. It's always a good idea to take the time to ensure that your app starts up nicely. This article offers tips and suggestions to help you achieve that goal, both when writing a new app and when porting an app from another platform to the Web.</p>
<h2 id="Starting_up_nicely">Starting up nicely</h2>
<p>Regardless of platform, it's always a good idea to start up as <strong>quickly</strong> 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 <strong>asynchronously</strong> as possible. That means not running all your startup code in a single event handler on the app's main thread.</p>
<p>Instead, you should write your code so that your app creates a <a href="/en-US/docs/DOM/Using_web_workers" title="/en-US/docs/DOM/Using_web_workers">Web worker</a> that does as much as possible in a background thread. Then, anything that must be done on the main thread should be broken up into small pieces so that the app's event loop continues to cycle while it starts up. This will prevent the app, browser, and/or device from appearing to have locked up.</p>
<p>Why is it important to be asynchronous? Other than the reasons suggested above, consider the impact of a non-responsive page or user interface. The user is unable to cancel if they launched your app by mistake. If the app is being run in a browser, it's possible the user may get an "unresponsive app" or "slow script" notification. You should present some kind of interface, such as a progress bar, so that the user knows how much longer they'll need to wait while your app starts up.</p>
<h2>Where there's a will...</h2>
<p>If you're starting your project from scratch, it's usually pretty easy to just write everything the "right way," making appropriate bits of the code asynchronous. All pure startup calculations should be performed in background threads, while you keep the run-time of main thread events as short as possible. Include a progress indicator so the user knows what's going on and how long they'll be waiting. In theory, anyway, it should be pretty easy to design your new app to start up nicely.</p>
<p>On the other hand, however, it can get tricky when you're porting an existing app to the Web. A desktop application doesn't need to be written in an asynchronous fashion because usually the operating system handles that for you, or the app is the only thing that matters which is currently running, depending on the operating environment. 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.</p>
<p>While you can use <a href="/en-US/docs/DOM/Using_web_workers" title="/en-US/docs/DOM/Using_web_workers">Web workers</a> to run even very large, long-duration chunks of <a href="/en-US/docs/JavaScript" title="/en-US/docs/JavaScript">JavaScript</a> code asynchronously, there's a huge caveat that: workers don't have access to <a href="/en-US/docs/WebGL" title="/en-US/docs/WebGL">WebGL</a> or audio, and they can't send synchronous messages to the main thread, so you can't even proxy those APIs to the main thread. 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.</p>
<p>However, even code like that can be made asynchronous, with a little work.</p>
<h2>Getting asynchronous</h2>
<p>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):</p>
<ul>
  <li>If you need to decode asset files (for example, decoding JPEG files and turning them into raw texture data for later use by WebGL), that's great to do in workers.</li>
  <li>When dealing with data supported by the browser (for example, decoding image data), use the decoders built into the browser or device rather than rolling your own or using one from the original codebase. The provided one is almost certainly significantly faster, and will reduce your app size to boot. In addition, the browser may automatically parallelize these decoders.</li>
  <li>Any data processing that can be done in parallel should be. Don't do one chunk of data after another; do them all at once when possible!</li>
</ul>
<p>The more stuff you can do asynchronously, the better advantage your app can take of multicore processors.</p>
<p>Once the initial loading is done and the app's main code starts to run, it's possible your app may necessarily 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 that can be done in chunks interspersed across multiple calls to your app's main loop, so that the main thread gets to handle input and the like.</p>
<p>Emscripten provides an API to help with this refactoring; for example, you can use <code>emscripten_push_main_loop_blocker()</code> 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.</p>
<p>That leaves, though, the problem of having to refactor your existing code to actually work that way. That can take some time.</p>
Revert to this revision