Benchmarks include forced layout time

This change to 'benchmarks' reports the time taken to perform a forced
layout after mounting the tree. Adding a forced layout to the stress
tests can surface how different approaches to styling may affect browser
render timings.

The total time displayed is now the sum of "scripting time" (previously
total time) and "layout time". The layout time is a reflection of the
time the browser takes to perform a style recalculation and relayout of
the document.

The Benchmark component now has a 'forceLayout' prop. When it is 'true'
a forced layout is triggered on componentDidUpdate. The time taken is
added to the sample's timing data.
This commit is contained in:
Nicolas Gallagher
2018-01-22 17:28:03 -08:00
parent a403244e67
commit efeaea70a9
7 changed files with 73 additions and 48 deletions

View File

@@ -100,6 +100,8 @@ export default class App extends Component {
libraryName={r.libraryName}
libraryVersion={r.libraryVersion}
mean={r.mean}
meanLayout={r.meanLayout}
meanScripting={r.meanScripting}
runTime={r.runTime}
sampleCount={r.sampleCount}
stdDev={r.stdDev}
@@ -130,6 +132,7 @@ export default class App extends Component {
<View ref={this._setBenchWrapperRef}>
<Benchmark
component={Component}
forceLayout={true}
getComponentProps={getComponentProps}
onComplete={this._createHandleComplete({
sampleCount,

View File

@@ -1,5 +1,7 @@
// @flow
/* global $Values */
/**
* @flow
*/
import * as Timing from './timing';
import React, { Component } from 'react';
import { getMean, getMedian, getStdDev } from './math';
@@ -12,8 +14,6 @@ export const BenchmarkType = {
UNMOUNT: 'unmount'
};
const emptyObject = {};
const shouldRender = (cycle: number, type: $Values<typeof BenchmarkType>): boolean => {
switch (type) {
// Render every odd iteration (first, third, etc)
@@ -66,7 +66,8 @@ const sortNumbers = (a: number, b: number): number => a - b;
type BenchmarkPropsType = {
component: typeof React.Component,
getComponentProps?: Function,
forceLayout?: boolean,
getComponentProps: Function,
onComplete: (x: BenchResultsType) => void,
sampleCount: number,
timeout: number,
@@ -84,13 +85,13 @@ type BenchmarkStateType = {
* TODO: documentation
*/
export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkStateType> {
_raf: ?Function;
_startTime: number;
_samples: Array<SampleTimingType>;
static displayName = 'Benchmark';
static defaultProps = {
getComponentProps: () => emptyObject,
sampleCount: 50,
timeout: 10000, // 10 seconds
type: BenchmarkType.MOUNT
@@ -101,8 +102,9 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
constructor(props: BenchmarkPropsType, context?: {}) {
super(props, context);
const cycle = 0;
const componentProps = props.getComponentProps({ cycle });
this.state = {
componentProps: props.getComponentProps({ cycle }),
componentProps,
cycle,
running: false
};
@@ -111,7 +113,9 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
}
componentWillReceiveProps(nextProps: BenchmarkPropsType) {
this.setState(state => ({ componentProps: nextProps.getComponentProps(state.cycle) }));
if (nextProps) {
this.setState(state => ({ componentProps: nextProps.getComponentProps(state.cycle) }));
}
}
componentWillUpdate(nextProps: BenchmarkPropsType, nextState: BenchmarkStateType) {
@@ -121,11 +125,20 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
}
componentDidUpdate() {
const { sampleCount, timeout, type } = this.props;
const { forceLayout, sampleCount, timeout, type } = this.props;
const { cycle, running } = this.state;
if (running && shouldRecord(cycle, type)) {
this._samples[cycle].end = Timing.now();
this._samples[cycle].scriptingEnd = Timing.now();
// force style recalc that would otherwise happen before the next frame
if (forceLayout) {
this._samples[cycle].layoutStart = Timing.now();
if (document.body) {
document.body.offsetWidth;
}
this._samples[cycle].layoutEnd = Timing.now();
}
}
if (running) {
@@ -139,8 +152,8 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
}
componentWillUnmount() {
if (this.raf) {
window.cancelAnimationFrame(this.raf);
if (this._raf) {
window.cancelAnimationFrame(this._raf);
}
}
@@ -148,7 +161,7 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
const { component: Component, type } = this.props;
const { componentProps, cycle, running } = this.state;
if (running && shouldRecord(cycle, type)) {
this._samples[cycle] = { start: Timing.now() };
this._samples[cycle] = { scriptingStart: Timing.now() };
}
return running && shouldRender(cycle, type) ? <Component {...componentProps} /> : null;
}
@@ -162,15 +175,18 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
const { getComponentProps, type } = this.props;
const { cycle } = this.state;
// Calculate the component props outside of the time recording (render)
// so that it doesn't skew results
const componentProps = getComponentProps({ cycle });
// make sure props always change for update tests
if (type === BenchmarkType.UPDATE) {
componentProps['data-test'] = cycle;
let componentProps;
if (getComponentProps) {
// Calculate the component props outside of the time recording (render)
// so that it doesn't skew results
componentProps = getComponentProps({ cycle });
// make sure props always change for update tests
if (type === BenchmarkType.UPDATE) {
componentProps['data-test'] = cycle;
}
}
this.raf = window.requestAnimationFrame(() => {
this._raf = window.requestAnimationFrame(() => {
this.setState((state: BenchmarkStateType) => ({
cycle: state.cycle + 1,
componentProps
@@ -182,10 +198,16 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
return this._samples.reduce(
(
memo: Array<FullSampleTimingType>,
{ start, end: endTime }: SampleTimingType
{ scriptingStart, scriptingEnd, layoutStart, layoutEnd }: SampleTimingType
): Array<FullSampleTimingType> => {
const end = endTime || 0;
memo.push({ start, end, elapsed: end - start });
memo.push({
start: scriptingStart,
end: layoutEnd || scriptingEnd || 0,
scriptingStart,
scriptingEnd: scriptingEnd || 0,
layoutStart,
layoutEnd
});
return memo;
},
[]
@@ -199,11 +221,13 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
this.setState(() => ({ running: false, cycle: 0 }));
const runTime = endTime - this._startTime;
const sortedElapsedTimes = samples
.map(({ elapsed }: { elapsed: number }): number => elapsed)
const sortedElapsedTimes = samples.map(({ start, end }) => end - start).sort(sortNumbers);
const sortedScriptingElapsedTimes = samples
.map(({ scriptingStart, scriptingEnd }) => scriptingEnd - scriptingStart)
.sort(sortNumbers);
const sortedLayoutElapsedTimes = samples
.map(({ layoutStart, layoutEnd }) => (layoutEnd || 0) - (layoutStart || 0))
.sort(sortNumbers);
const mean = getMean(sortedElapsedTimes);
const stdDev = getStdDev(sortedElapsedTimes);
onComplete({
startTime: this._startTime,
@@ -214,8 +238,10 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
min: sortedElapsedTimes[0],
median: getMedian(sortedElapsedTimes),
mean,
stdDev
mean: getMean(sortedElapsedTimes),
stdDev: getStdDev(sortedElapsedTimes),
meanLayout: getMean(sortedLayoutElapsedTimes),
meanScripting: getMean(sortedScriptingElapsedTimes)
});
}
}

View File

@@ -1,4 +1,6 @@
// @flow
/**
* @flow
*/
export type BenchResultsType = {
startTime: number,
endTime: number,
@@ -9,20 +11,21 @@ export type BenchResultsType = {
min: number,
median: number,
mean: number,
stdDev: number,
p70: number,
p95: number,
p99: number
stdDev: number
};
export type SampleTimingType = {
start: number,
end?: number,
elapsed?: number
scriptingStart: number,
scriptingEnd?: number,
layoutStart?: number,
layoutEnd?: number
};
export type FullSampleTimingType = {
start: number,
end: number,
elapsed: number
scriptingStart: number,
scriptingEnd: number,
layoutStart?: number,
layoutEnd?: number
};

View File

@@ -15,8 +15,9 @@ class ReportCard extends React.PureComponent {
libraryName,
sampleCount,
mean,
meanLayout,
meanScripting,
stdDev,
runTime,
libraryVersion
} = this.props;
@@ -38,9 +39,8 @@ class ReportCard extends React.PureComponent {
<Text style={[styles.bold, styles.monoFont]}>
{fmt(mean)} ±{fmt(stdDev)} ms
</Text>
<Text style={[styles.monoFont, styles.centerText]}>
<Text style={styles.smallText}>Σ = </Text>
<Text>{Math.round(runTime)} ms</Text>
<Text style={[styles.smallText, styles.monoFont]}>
(S/L) {fmt(meanScripting)} / {fmt(meanLayout)} ms
</Text>
</Fragment>
) : (

View File

@@ -1,9 +1,5 @@
/* eslint-disable react/prop-types */
/**
* @flow
*/
import { bool } from 'prop-types';
import React from 'react';
import { StyleSheet, Text } from 'react-native';

View File

@@ -1,5 +1,3 @@
/* @flow */
import { type Component } from 'react';
import packageJson from '../package.json';