https://vimeo.com/162850953

The purpose of this research is to give a clear overview of how modern Javascript engines run, comparison between the different engines and a quick guide on writing well-performing code.

This research analyses the Javascript engines - Chrome V8 [1], Microsoft Chakra [2] and Microsoft Chakra Core [3], and put in practices to ensure that these engines can easily profile and optimize the codes you have written, allowing better performance.

Test Platform Specifications

  • Operating System: Windows Server 2016 Technical Preview 2
  • Disk: Corsair Force GS 128GB SSD
  • CPU: Intel i3-4130 @ 3.40 GHz
  • RAM: 8GB
  • Chrome: 49.02623.112 m
  • Edge: 20.10514.0.0
  • NodeJS: 5.80
  • NodeChakra: 6.0.0-pre6

Summary

  • Both engines profile code and perform Just in time (JIT) compilation which produces optimized machine codes.
  • V8 performs slightly better than Chakra and Chakra Core.
  • Write predictable code so that they can be easily profiled.

Javascript Engines

As a quick overview, Javascript engines are embedded in browsers and web servers, such as NodeJS [4], to allow run-time compilation and execution of Javascript code. These engines consist of an interpreter and an optimized compiler.

Both Chrome V8 and Chakra Core are open source Javascript engines, which means developers and researchers can "look under the hood" to understand what exactly is happening. The following sections provide a detailed analysis of both Javascript engines.

Google Chrome V8

The V8 engine has an interpreter named "Ignition" [5]. This interpreter is used for interpreting and executing low level bytecode. Bytecodes, although slower, are smaller than machine codes and requires lesser compilation time.

In order to compile Javascript to bytecode, the Javascript code has to be parsed to generate its Abstract Syntax Tree (AST). The interpreter has an accumulator register, which allows it reduce the size of the bytecode. The overall design makes Ignition a highly efficient interpreter.

Whereas its optimizing JIT compiler is named "TurboFan" [6]. TurboFan will profile the code and see if it is used multiple times throughout the entire Javascript execution. If it is, the code will be dynamically optimized immediately into machine code, without any intermediate binary code. If it is a one-time executed "non-hot" code, it will only be compiled into binary code.

By reducing unnecessary generation of machine code, the Javascript engine will be able to run more efficiently. The profiling makes use of hidden classes, which are classes that can be instantiated to create objects with fixed variable offsets. Fixed offsets, rather than dynamic lookup, allows codes to be read in a very efficient manner without having to resolve to a memory location for a variable.

However, if the code being profiled is acting in a way which is not as predicted, the engine will fallback to normal bytecode interpretation, and this causes it to slow down. Only after some period of time will V8 then attempt to profile other codes. Therefore, developers should always try to write their algorithms and code that runs in a predictable manner.

Garbage collection is also done in a "stop-the-world", generational way. This means that before the JavaScript engine does garbage collection, all processing of JavaScript will be paused and the garbage collector will find objects and data that are no longer referenced and collect them. This ensures that garbage collection is done in an accurate and efficient way.

Chakra Core

Chakra Core has certain components of Chakra and is modified to report certain data differently [7]. Instead of COM-based diagnostic APIs, Chakra Core provides JSON-based diagnostic APIs which allow more applications with JSON parsers to support Chakra Core.

The architecture is also designed to have multi-tier support. This allows Chakra Core to do parallel JIT compilation and utilize as much resources as possible to finish its task, as fast as it can. Chakra Core first reads through the Javascript code syntax and parses it to generate its AST. After the AST is generated, the code is passed to the bytecode generator and then the bytecode gets profiled. This is different from V8 which has a decision process that decides whether a piece of code should be profiled and optimized or should be turned into bytecode.

During the profiling process, the interpreter attempts to send the code to parallel JIT compilers. There are two types of JIT compilers available: simple JIT compiler, and full JIT compiler.

During run time, the Javascript code will undergo a simple JIT compile, where the codes are compiled quickly without much optimization. Whilst full JIT will also happen concurrently, compiling the Javascript code in an optimized manner --- the full JIT takes more time but produces better machine code that is generated based on the profile produced by the profiling interpreter.

If full JIT compilation fails because the code does something that is out of its profile, the engine will bailout to run the codes that are JIT'd using the simple JIT compiler. To prevent bailout of full JIT to simple JIT codes from happening, developers should write their code so that that it does processing in a predictable manner.

As for garbage collection, it is done in a generational mark-and-sweep manner. When garbage collection happens, a foreground and background thread will be spawned to carry out different executions. The background garbage collector will do a mark, rescan and mark to find objects that should be cleaned up.

Benchmark

The benchmark and tests were performed using the Octane 2.0 Benchmark [8], which measures the performance of a Javascript engine through running code that is used in the modern era's web applications.

Benchmarks are an incomplete representation of the performance of the engine and should only be used for a rough gauge. The benchmark results may not be 100% accurate and may also vary from platforms to platforms.

Through extensive, iterative tests on Google Chrome on V8 engine, NodeJS on V8 engine, Microsoft Edge on Chakra engine and NodeJS on Chakra Core engine, here are the results.

Google Chrome V8

NodeJS V8 Octane 2.0 Score: 16,639

Google Chrome Browser V8 Octane 2.0 Score: 21,800

Chakra

NodeJS Chakra Core Octane 2.0 Score: 15,462

Microsoft Edge Chakra Octane 2.0 Score: 20,700

Examples

The following examples show how Javascript codes can be written to avoid breaking profiling, therefore, improving performance. Detailed examples can also be found on Github [9].

/* In this example, we show that good code should not create new variables of the object. 
 * This is to prevent an additional hidden class from being created.
*/

// Bad code
function Person(name, age) {
    this.name = name;
    this.age = age;
}

var mary = new Person("mary", 22); 
var bryan = new Person("bryan", 22); 
bryan.blood = "AB"; // Blood was never declared, new hidden class "person" has to be declared

// Good code 
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.blood = ""; // Blood is declared
}

var mary = new Person("mary", 22); 
var bryan = new Person("bryan", 22); 
bryan.blood = "AB";

Performance Measurement

There are several tools out there which can help you out with testing out the performance of your Javascript code. An accurate and convenient way to do it is to make use of the Javascript function - console.time [10] or performance.now [11], which works on most modern browsers and newer NodeJS versions. For a more accurate, non-DOM based performance testing, use the V8 internal profiler [12].

Sources

[1] - https://developers.google.com/v8/
[2] - http://www.thewindowsclub.com/microsofts-new-javascript-engine-codenamed-chakra-for-internet-explorer-9
[3] -  https://github.com/microsoft/ChakraCore
[4] - https://nodejs.org/en/
[5] - https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/edit?ts=56f27d9d#heading=h.6jz9dj3bnr8t
[6] - http://v8project.blogspot.de/2015/07/digging-into-turbofan-jit.html
[7] - https://github.com/Microsoft/ChakraCore/wiki/Architecture-Overview
[8] - https://developers.google.com/octane/
[9] - https://github.com/GIANTCRAB/Javascript-Performance-Test/
[10] - https://developer.mozilla.org/en-US/docs/Web/API/Console/time
[11] - https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
[12] - https://github.com/v8/v8/wiki/V8%20Profiler

Last updated: October 18, 2018