Squeezing v8 startup time

On my day to day job, v8 in present most of the time. One philosophical foundation of our product is startup time, and all time taken from startup to the first frame drawn must be lowered as much as possible.

This startup time is composed of many different steps, from GL context initialization, to script parsing, etc. Some of these steps can’t simply be avoided or its time can’t be lowered because it is system dependent (like creating an OpenGL context), but other’s can be addressed, and every millisecond on a mobile device is accounted for. While medium/high-end devices like a Nexus 5x have pretty decent execution times from startup to frame shown (<250ms), lower end ones like a Motorola XT’s are not so good.

A few approaches to lower these times are:

Use snapshot

Same code executed on v8 5.6 with or without snapshot results in dramatic v8 initialisation times improvement.
While Nexus 5x impact is relatively low: 32 vs 59milliseconds with and without snapshot, on the lower end Motorola device, the difference is 85 vs 560 milliseconds. Just v8’s initialisation time is half the time a user perception needs to flag a bad user experience.
v8 snapshots are enabled by default, and will require you to supply user-side stub code for loading general snapshots, but totally worth it.
These are numbers for just bootstrapping v8 w/o user code.
I personally include the snapshots as a natively linked library. These could also be loaded from disk, but i/o can impose its toll.

Upgrade v8 version

Compiling a new v8 version has almost halved scripts’ parse and execution times. From v8 4.9 to v8 5.6, parse/execution time of a big script (5000 js loc/400Kb) is mostly halved at no cost. From 160 to 75 ms on the Motorola, and from 60 to 35 ms in the Nexus 5x.
Bumping one simple v8 version number does the magic. It also behaves much better on memory for devices with 512Mb of memory, etc.
Unfortunately, bumping v8 version can’t keep up with lowering parse/execution times forever though.

Code load, parse and execution

Parse/execution time can also be affected by how we load code into v8. Chrome has a defer modifier for a script declaration. How this translated into v8 world is simple: v8 expect the javascript code to be streamed and will thus be incrementally parsed and then on the fly.

My tests show that for big browserified projects, I get up to a 10-20% speed up in parse/execution times for the Motorola, and for small files in high-end devices, it can be negligible (simply because the higher end device is already fast enough).

At native code level, this translates in adding more infrastructure. v8 enters a streaming mode, in which the developer must feed script contents to v8 on demand. The setup is not straightforward, and a background thread is needed to govern streaming javascript content. Here’s v8 headers for the needed setup:

/**
 * Source code which can be streamed into V8 in pieces. 
 * It will be parsed while streaming. 
 * It can be compiled after the streaming is complete.
 * StreamedSource must be kept alive while the streaming
 * task is ran (see ScriptStreamingTask below).
 */
class V8_EXPORT StreamedSource {
   ...
}
/**
 * A streaming task which the embedder must run on a 
 * background thread to stream scripts into V8. Returned by 
 * ScriptCompiler::StartStreamingScript.
 */
class ScriptStreamingTask {
 public:
  virtual ~ScriptStreamingTask() {}
  virtual void Run() = 0;
};
/**
 * Returns a task which streams script data into V8, or NULL 
 * if the script cannot be streamed. The user is responsible 
 * for running the task on a background thread and deleting it. 
 * When ran, the task starts parsing the script, and it will
 * request data from the StreamedSource as needed. When
 * ScriptStreamingTask::Run exits, all data has been streamed
 * and the script can be compiled (see Compile below).
 *
 * This API allows to start the streaming with as little data 
 * as possible, and the remaining data (for example, the 
 * ScriptOrigin) is passed to Compile.
 */
static ScriptStreamingTask* StartStreamingScript(
    Isolate* isolate, StreamedSource* source,
    CompileOptions options = kNoCompileOptions);

Generalities

I assume you have already minimised/obfuscated your javascript code, and that your images/textures/assets have been compressed, power vr tooled, etc. etc.

Published by ibon

Chocolate engineer, software eater. Data visualisation at Workday. Past: Platochat, SdkBox, Chukong, Ludei.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: