Performance Timing

Goals

Challenges

Existing implementations

React timing marks

In version v15.4.0, React added support for marker on the timeline with the different lifecycle events (changelog). This functionality is disabled by default, can only get turned on in DEV mode using the ?react_perf query string. See example at: https://olz8m97rz5.codesandbox.io/?react_perf.

Note: Since v16, the markers are on by default in DEV mode.

Observed lifecycle hooks:

React performance tools (deprecated with v16)

Before releasing the timing marks, React offered the capability to introspect performance timing via the Perf object. This object is only available in DEV mode. The object exposes the following APIs:

Observed lifecycle hooks: render and sum of all the lifecycle hooks Implementation: ReactPerf.js

You can find more detail in the React documentation: performance tools

Vue

After React announcement, In version 2.2.3, Vue added support for markers (config). This functionality is also disabled by default, and can be turned on using a global config flag Vue.config.performance = true; in DEV mode. Originally the functionality was enabled by default, it then get disabled because of the performance impact issue#5174. See example at: https://l4xqw3qn5z.codesandbox.io/

Observed lifecycle hooks: init, render, patch Implementation: perf.js

Proposal

What?

In the case of LWC, the following hooks are good candidates for performance timing: constructor, patch, connectedCallback, renderedCallback, errorCallback, disconnectedCallback.

Where?

In order to put the measurement in place around the following hooks, it would require to put marks directly in the engine. Using services would have been preferable in order to break the coupling. Since the current service API doesn't expose pre/post hooks for each life cycle event, it make it impossible to measure the actual lifecycle event duration.

How?

Measurement

In both framework, the timing marks is based on performance.mark and performance.measure. The following snippet is extracted from Vue implementation:

/** perf.js */
const mark = tag => performance.mark(tag);
const measure = (name, startTag, endTag) => {
    performance.measure(name, startTag, endTag);

    // Avoid performance entry buffer overflow
    performance.clearMarks(startTag);
    performance.clearMarks(endTag);
    performance.clearMeasures(name);
};

/** init.js */
mark('lwc-perf-start:render');

// ... Do the render work ...

mark('lwc-perf-end:render');
measure('render', 'lwc-perf-start:render', 'lwc-perf-end:render');

The Chrome devtool timeline surface the performance.measures as a flamechart above the javascript callstack. Firefox and Safari doesn't display the performance measures in the timeline. IE11 and Edge shows markers in a waterfall manner in their timeline.

Adding performance marks and measures has a visible impact on performance. Because of the overhead on IE11, the performance markers should be disabled by default and only turned on when requested.

# marks Chrome Firefox Safari IE11
1000 3.5 1.8 2.3 15.2
100000 352.5 166.3 85.5 1454.6

Test: https://jsfiddle.net/hd4mzwwo/

Enablement

For now, the performance timing will be enabled by default for all mode except production. In production mode, the code for performance timing will get stripped from the source code.

Consumption

The PerformanceObserver API is used to observe performance events. The PerformanceObserver constructor accepts a callback methods invoked when a performance event is recorded. An instance of the PerformanceObserver start listening to performance events after calling the observe method with a list of entry type to listen.

Even if this API is not available in IE11, it's possible to create a polyfill specifically for performance.mark and performance.measure.

const observer = new PerformanceObserver(list => {
    console.log(list.getEntries())
});

observer.observe({
    entryTypes: ['measure']
});

Note: After running some tests, it appears the PerformanceObserver doesn't have a maximum buffer size.

undefined