My favorites | English | Sign in

Faster JavaScript with Closure Tools New!

Using the V8 Engine

Introduction

JavaScript performance across browsers can vary greatly. An O3D application may perform well in one browser while not performing adequately in another. There are a number of causes for this difference. Some JavaScript engines interpret JavaScript code, while others compile it to native code. Some browsers host plug-ins in the same process as the browser, while others host them in a separate process, leading to a high overhead for calls between JavaScript and the O3D API.

To mitigate these issues, the O3D plug-in embeds the V8 JavaScript engine. This is the same high-performance JavaScript engine found in the Chrome browser. An O3D application using the embedded JavaScript engine instead of or in addition to the browser's own JavaScript engine may see improved performance, more consistent performance across browsers, and sometimes significantly lower overhead when calling between JavaScript and the O3D API. A disadvantage of this approach is it is no longer possible to debug such JavaScript code in the browser's JavaScript debugger.

High-Level Interface

There are two interfaces to the V8 engine, one higher level and one lower level. The high-level interface is provided by the o3djs utility code and is available when you require o3djs.util. Then, you select the engine by calling o3djs.util.setMainEngine() and initialize the client by calling o3djs.util.makeClients(). If you do not call setMainEngine(), JavaScript will run in the browser engine by default. Here is an example of running code in V8:

function init() {
  o3djs.util.setMainEngine(o3djs.util.Engine.V8);
  o3djs.util.makeClients(initStep2);

// This function and all functions it calls will run in V8 rather than the browser.
function initStep2(clientElements) {
  // ...
}

The contents of all the script tags will be evaluated in V8 in addition to the browser. All the the modules requested through o3djs.require will also be available in V8.

To run in V8, there are a few other steps to take. First of all, make sure none of the code in your script tags does anything other than define variables and functions. The problem is, if the code outside of function definitions actually does anything, it will happen twice—once when the browser first evaluates it and a second time when it gets uploaded to V8. Here is an example:

// At top level
window.onload = init;
window.onunload = uninit;

This code will actually be executed twice. The first time, onload and onunload will be made to reference the browser's copies of the init and uninit functions. The second time, they will be made references to V8's. It would be better to set up the callbacks in the body tag, so that they are always set up to point to the browser's copies. It is best to always run these two functions in the browser:

<body onload="init()" onunload="uninit()">
  ...
</body>

Another thing to consider is that separate global variables with the same name can coexist, one in V8 and one in the browser. V8 can read all the browser's global variables until it tries to modify them. At that point, a new global variable, only visible to V8 is created, which hides the browser's variable from V8. It is sometimes necessary to synchronize their values. This can be done from V8 like this:

// Running in V8
function initStep2() {
  g_client = clientElements[0].client; // Set V8's copy of the variable.
  window.g_client = g_client;          // Set the browser's copy of the variable to be the same as V8's.
}

This shows how V8 can be sure it is accessing the browser's copy of a global variable by accessing it through window. There is no straightforward way to synchronize from the browser. Do it from V8.

When V8 is set as the main engine, one and only one of the o3d divs must have the id "o3d". This is how o3djs knows which plug-in instance to invoke the makeClients() callback in:

<div id="o3d" style="width: 800px; height: 600px;"></div>

Low-Level Interface

With the low-level interface, JavaScript code is not automatically pulled out of script tags and evaluated in V8. Instead, you pass a string of JavaScript code to the eval method on the plug-in object. If the variable g_plugin holds a reference to the plug-in, then this code:

var result = g_plugin.eval("2+2");

will pass the expression "2+2" from the browser's JavaScript engine to the embedded V8 JavaScript engine. The embedded engine will evaluate the result to be 4. The number 4 is then returned to the browser's JavaScript engine and stored in the result variable.

Although this simple example demonstrates how to use the embedded V8 engine, it will probably not improve performance. In fact, it will most likely be much worse than simply writing:

var result = 2+2;

The reason is that there is some overhead for transitioning control from the browser JavaScript engine, evaluating the string as JavaScript in the V8 engine, and then transitioning back to the browser. It is only worthwhile running JavaScript in the V8 engine when the time savings for doing so balances the cost of the overhead. For example, a loop iterating many hundreds of times would be worth the transition time.

Evaluating a Function as a String

It is rather inconvenient to have to construct a string containing a complicated sequence of JavaScript code. O3D provides a means of evaluating JavaScript that is more convenient in these cases. It is a two-step process:

  1. Create a V8 function object that optionally takes arguments and assign it to a browser JavaScript variable.
  2. Whenever you want to invoke that V8 function, call the returned variable with any applicable arguments.

For example, here is how you would define the variable for the function:

var func = g_plugin.eval("function(a, b) { return a + b; }");

Usually you only need to call this first step once. From now on, it is possible to evaluate an addition operation in the V8 engine by calling the variable func as a function. For example:

var result = func(2, 2);

This call has the same effect as the earlier example, but it can be easier in some cases. In addition to standalone functions, the same trick works with object methods. If the V8 engine creates a new object and returns it to the browser, whenever the browser invokes a method on that object, control automatically transitions from the browser to the V8 engine, where the method will be evaluated. For example:

var mathHelper = g_plugin.eval("{ add: function(a, b) { return a + b; } }");
var result = mathHelper.add(2, 2);

Callback Functions

In some cases, you will want to evaluate a function to do complex computation in the V8 engine and then return control to the browser to perform additional operations, such as calling an Alert or logging data to the console. Here is an example of how the browser could pass a callback function in to the V8 engine, and the V8 engine could invoke the callback to the browser. For example:

 var greatestCommonDivisor = g_plugin.eval(
    "function(a, b, logCallback) {" +
    "  if (a === 0)" +
    "    return b;" +
    "  while (b !== 0) {" +
    "    if (a > b) {" +
    "      a -= b;" +
    "      logCallback('a = ' + a);" +
    "    } else {" +
    "      b -= a;" +
    "      logCallback('b = ' + b);" +
    "    }" +
    "  }" +
    "  return a" +
    "}");

  function logResult(r) {
    var textNode = document.createTextNode(String(r));
    var paragraph = document.createElement('p');
    paragraph.appendChild(textNode);
    document.body.appendChild(paragraph);
  }

  var result = greatestCommonDivisor(151698344, 205238936, logResult);
  alert(result);

Note: In JavaScript, a string cannot span more than one line, so the + signs are necessary to make the entire eval expression one string. Alternatives to this notation are to put the text in a <text> element or in a separate file that is referenced by the function.

The plugin Variable

Within V8, there is a special variable called plugin. This variable always references the plugin object and can be used to locate other useful objects. For example:

var o3d = plugin.o3d;

Accessing Browser Variables from V8

A V8 engine can read all the global variables in the browser's JavaScript engine, but it cannot write to them. Any changes the V8 engine makes are visible only to the plug-in instance for a given V8 engine (see the following diagram). The browser cannot directly read or write to the V8 engine's global variables.

Accessing the DOM from V8

In most browsers, it is possible to access and manipulate the DOM from V8. For example, the following code works when run from V8:

var element = document.getElementById("myElement");

element.value = "foo";

Unfortunately, at the present time, this code does not work in Internet Explorer. From V8, it is not possible to invoke a DOM method in Internet Explorer. However, it is possible to read and write DOM properties in Internet Explorer.

As a partial workaround, since it is one of the most common DOM methods, we provide a utility function for getElementById() that works in Internet Explorer:

var element = o3djs.util.getElementById("myElement");

element.value = "foo";