Blog 4 min read

Boost Performance with V8 Engine: A Design Choices Study

Share this article
Boost Performance with V8 Engine: A Design Choices Study

The V8 engine is Google's open source, high-performance JavaScript engine written in C++. Alongside Google Chrome, it can also be found in MongoDb, Node.js, and many other popular applications.

It’s very interesting to discover what makes V8 so fast and which solutions were used to achieve this goal.

OOP, design patterns, and performance

There is an odd conviction among some C/C++ programmers: they seem to think that using OOP and design patterns decreases their application performance. V8 is a good example proving that this is not true. V8 implements many design patterns, and it’s well optimized.

Here are some of the patterns used:

Factory

When the JavaScript engine executes a script, it creates an instance for each variable, function or array encountered. JSObject is the parent class of all these kinds of objects.

Here’s the list of all classes inheriting from JSObject:

v1

V8 implements a factory class to create the objects needed, and the Factory::NewJsObject method is used for this purpose.

Here are all the methods that use it:

v4

The factory is not used directly by the V8 classes; it’s invoked from the Heap class, which adds another level of indirection to the implementation.

Visitor

As well explained in the visitor wiki page:

The visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.

Like the factory pattern, this pattern also adds some indirection to the implementation, but it makes the code more readable and maintainable.

The V8 source code contains many classes implementing the visitor pattern.

v2

Even though the V8 developers have to optimize its execution, they don’t mind if some indirection is added to the code. It’s true that using design patterns and some C++ mechanisms can impact performance due to the indirection added by their implementation, but that’s more a matter of micro-optimization. Significant macro-optimization depends more on your design choices, specific to your application needs.

V8 design choices to optimize its execution

1- Hidden classes and fast property access

JavaScript is a dynamic programming language: properties can be added to, and deleted from, objects on the fly. This means an object’s properties are likely to change.

As mentioned before, JSObject is the parent class of JSFunction, which represents a JavaScript function, or JSValue, which represents a JavaScript value. However, there’s no class inheriting from JSObject that represents a class like Function or Value. Most JavaScript engines use a dictionary-like data structure as storage for object properties; each property access requires a dynamic lookup to resolve the property’s location in memory.

This approach makes accessing properties in JavaScript typically much slower than accessing instance variables in programming languages like Java and Smalltalk. In these languages, instance variables are located at fixed offsets determined by the compiler due to the fixed object layout defined by the object’s class. Access is simply a matter of a memory load or store, often requiring only a single instruction.

V8 uses the hidden class concept to reduce the time required to access JavaScript properties. V8 does not use dynamic lookup to access properties. Instead, V8 dynamically creates hidden classes behind the scenes.

2- Dynamic machine code generation

V8 compiles JavaScript source code directly into machine code when it is first executed. There are no intermediate bytecodes and no interpreter. Property access is handled by inline cache code that may be patched with other machine instructions as V8 executes.

3- Efficient garbage collector

V8 reclaims memory used by objects that are no longer required, in a process known as garbage collection. To ensure fast object allocation, short garbage collection pauses, and no memory fragmentation, V8 employs a stop-the-world, generational, accurate garbage collector. This means that V8:

  • stops program execution when performing a garbage collection cycle.
  • processes only part of the object heap in most garbage collection cycles, which minimizes the impact of stopping the application.
  • always knows exactly where all objects and pointers are in memory, which avoids falsely identifying objects as pointers — something that can result in memory leaks.

Conclusion

Choosing not to use OOP and design patterns because optimization is your first priority is maybe a bad idea: you might gain a few microseconds but lose the readability and maintainability of your code.

Significant macro-optimization depends more on your design choices, specific to your application needs.

Share this article