JavaScript Performance: Node CLI flags

Date: August 6, 2020 Last modified: August 6, 2020

These are some notes on performance engineering related to generating insights into Node.js (v8) performance.

All material here is specific to the v8 engine found in Node.js, Chrome, and Opera.

Generally useful things to learn to tune JavaScript (for the v8 engine) is natives syntax. You can run JS with natives syntax directives sprinkled throughout using the Node.js command-line:

  • --allow-natives-syntax

Read more on available natives syntax directives available from the source.

Another generally useful tool for JavaScript performance engineering is the User Timing API.

Speculative optimization with optimization and deoptimization

With speculative optimization we have the bytecode interpreter look at how a function is used (e.g. what types of values are passed as arguments) to see if it can delegate to the optimizing compiler for specialization or related optimization strategies after observing call usages for patterns.

If the observations that the interpreter uses to delegate to the optimizing compiler gets nullified after delegating to the optimizer then that unit of code (e.g. function) gets deoptimized and a request to the interpreter to generate general bytecode with no optimizations is made incurring more work and slowing the process down.

To understand what gets optimized by the turbofan optimizer and what gets delegated back for deoptimization from the turbofan to bytecode generator we can use:

  • --trace-opt

  • --trace-deopt

The following terms may be used in different contexts to mean similar things but the following are defined in the bounded context of speculative optimization.

Monomorphic

This is where a function handles a specific type only and all call sites only pass values of that type.

Using TypeScript we can ensure consistency of our call sites with appropriate type annotations:

const add = (x: number, y: number): number => x + y;

Polymorphic

This is where a function handles a range of types effectively making an optimized switch statement for the cases. The more cases the more the optimizer loses its effectiveness and gives up where it gets "demoted" to megamorphic.

Megamorphic

Anything goes! This breaks the optimizer, big time. Avoid this at all costs.

Inlining

Functions

Node.js command-line options related to function inlining are:

  • --trace-turbo-inlining