mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-01 18:15:13 +00:00
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:
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
[ignore]
|
[ignore]
|
||||||
<PROJECT_ROOT>/.*/__tests__/.*
|
<PROJECT_ROOT>/.*/__tests__/.*
|
||||||
<PROJECT_ROOT>/packages/benchmarks/.*
|
|
||||||
<PROJECT_ROOT>/packages/.*/dist/.*
|
<PROJECT_ROOT>/packages/.*/dist/.*
|
||||||
<PROJECT_ROOT>/website/.*
|
<PROJECT_ROOT>/website/.*
|
||||||
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
|
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export default class App extends Component {
|
|||||||
libraryName={r.libraryName}
|
libraryName={r.libraryName}
|
||||||
libraryVersion={r.libraryVersion}
|
libraryVersion={r.libraryVersion}
|
||||||
mean={r.mean}
|
mean={r.mean}
|
||||||
|
meanLayout={r.meanLayout}
|
||||||
|
meanScripting={r.meanScripting}
|
||||||
runTime={r.runTime}
|
runTime={r.runTime}
|
||||||
sampleCount={r.sampleCount}
|
sampleCount={r.sampleCount}
|
||||||
stdDev={r.stdDev}
|
stdDev={r.stdDev}
|
||||||
@@ -130,6 +132,7 @@ export default class App extends Component {
|
|||||||
<View ref={this._setBenchWrapperRef}>
|
<View ref={this._setBenchWrapperRef}>
|
||||||
<Benchmark
|
<Benchmark
|
||||||
component={Component}
|
component={Component}
|
||||||
|
forceLayout={true}
|
||||||
getComponentProps={getComponentProps}
|
getComponentProps={getComponentProps}
|
||||||
onComplete={this._createHandleComplete({
|
onComplete={this._createHandleComplete({
|
||||||
sampleCount,
|
sampleCount,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// @flow
|
|
||||||
/* global $Values */
|
/* global $Values */
|
||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
import * as Timing from './timing';
|
import * as Timing from './timing';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { getMean, getMedian, getStdDev } from './math';
|
import { getMean, getMedian, getStdDev } from './math';
|
||||||
@@ -12,8 +14,6 @@ export const BenchmarkType = {
|
|||||||
UNMOUNT: 'unmount'
|
UNMOUNT: 'unmount'
|
||||||
};
|
};
|
||||||
|
|
||||||
const emptyObject = {};
|
|
||||||
|
|
||||||
const shouldRender = (cycle: number, type: $Values<typeof BenchmarkType>): boolean => {
|
const shouldRender = (cycle: number, type: $Values<typeof BenchmarkType>): boolean => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
// Render every odd iteration (first, third, etc)
|
// Render every odd iteration (first, third, etc)
|
||||||
@@ -66,7 +66,8 @@ const sortNumbers = (a: number, b: number): number => a - b;
|
|||||||
|
|
||||||
type BenchmarkPropsType = {
|
type BenchmarkPropsType = {
|
||||||
component: typeof React.Component,
|
component: typeof React.Component,
|
||||||
getComponentProps?: Function,
|
forceLayout?: boolean,
|
||||||
|
getComponentProps: Function,
|
||||||
onComplete: (x: BenchResultsType) => void,
|
onComplete: (x: BenchResultsType) => void,
|
||||||
sampleCount: number,
|
sampleCount: number,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
@@ -84,13 +85,13 @@ type BenchmarkStateType = {
|
|||||||
* TODO: documentation
|
* TODO: documentation
|
||||||
*/
|
*/
|
||||||
export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkStateType> {
|
export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkStateType> {
|
||||||
|
_raf: ?Function;
|
||||||
_startTime: number;
|
_startTime: number;
|
||||||
_samples: Array<SampleTimingType>;
|
_samples: Array<SampleTimingType>;
|
||||||
|
|
||||||
static displayName = 'Benchmark';
|
static displayName = 'Benchmark';
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
getComponentProps: () => emptyObject,
|
|
||||||
sampleCount: 50,
|
sampleCount: 50,
|
||||||
timeout: 10000, // 10 seconds
|
timeout: 10000, // 10 seconds
|
||||||
type: BenchmarkType.MOUNT
|
type: BenchmarkType.MOUNT
|
||||||
@@ -101,8 +102,9 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
constructor(props: BenchmarkPropsType, context?: {}) {
|
constructor(props: BenchmarkPropsType, context?: {}) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const cycle = 0;
|
const cycle = 0;
|
||||||
|
const componentProps = props.getComponentProps({ cycle });
|
||||||
this.state = {
|
this.state = {
|
||||||
componentProps: props.getComponentProps({ cycle }),
|
componentProps,
|
||||||
cycle,
|
cycle,
|
||||||
running: false
|
running: false
|
||||||
};
|
};
|
||||||
@@ -111,7 +113,9 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: BenchmarkPropsType) {
|
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) {
|
componentWillUpdate(nextProps: BenchmarkPropsType, nextState: BenchmarkStateType) {
|
||||||
@@ -121,11 +125,20 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { sampleCount, timeout, type } = this.props;
|
const { forceLayout, sampleCount, timeout, type } = this.props;
|
||||||
const { cycle, running } = this.state;
|
const { cycle, running } = this.state;
|
||||||
|
|
||||||
if (running && shouldRecord(cycle, type)) {
|
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) {
|
if (running) {
|
||||||
@@ -139,8 +152,8 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.raf) {
|
if (this._raf) {
|
||||||
window.cancelAnimationFrame(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 { component: Component, type } = this.props;
|
||||||
const { componentProps, cycle, running } = this.state;
|
const { componentProps, cycle, running } = this.state;
|
||||||
if (running && shouldRecord(cycle, type)) {
|
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;
|
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 { getComponentProps, type } = this.props;
|
||||||
const { cycle } = this.state;
|
const { cycle } = this.state;
|
||||||
|
|
||||||
// Calculate the component props outside of the time recording (render)
|
let componentProps;
|
||||||
// so that it doesn't skew results
|
if (getComponentProps) {
|
||||||
const componentProps = getComponentProps({ cycle });
|
// Calculate the component props outside of the time recording (render)
|
||||||
// make sure props always change for update tests
|
// so that it doesn't skew results
|
||||||
if (type === BenchmarkType.UPDATE) {
|
componentProps = getComponentProps({ cycle });
|
||||||
componentProps['data-test'] = 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) => ({
|
this.setState((state: BenchmarkStateType) => ({
|
||||||
cycle: state.cycle + 1,
|
cycle: state.cycle + 1,
|
||||||
componentProps
|
componentProps
|
||||||
@@ -182,10 +198,16 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
return this._samples.reduce(
|
return this._samples.reduce(
|
||||||
(
|
(
|
||||||
memo: Array<FullSampleTimingType>,
|
memo: Array<FullSampleTimingType>,
|
||||||
{ start, end: endTime }: SampleTimingType
|
{ scriptingStart, scriptingEnd, layoutStart, layoutEnd }: SampleTimingType
|
||||||
): Array<FullSampleTimingType> => {
|
): Array<FullSampleTimingType> => {
|
||||||
const end = endTime || 0;
|
memo.push({
|
||||||
memo.push({ start, end, elapsed: end - start });
|
start: scriptingStart,
|
||||||
|
end: layoutEnd || scriptingEnd || 0,
|
||||||
|
scriptingStart,
|
||||||
|
scriptingEnd: scriptingEnd || 0,
|
||||||
|
layoutStart,
|
||||||
|
layoutEnd
|
||||||
|
});
|
||||||
return memo;
|
return memo;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@@ -199,11 +221,13 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
this.setState(() => ({ running: false, cycle: 0 }));
|
this.setState(() => ({ running: false, cycle: 0 }));
|
||||||
|
|
||||||
const runTime = endTime - this._startTime;
|
const runTime = endTime - this._startTime;
|
||||||
const sortedElapsedTimes = samples
|
const sortedElapsedTimes = samples.map(({ start, end }) => end - start).sort(sortNumbers);
|
||||||
.map(({ elapsed }: { elapsed: number }): number => elapsed)
|
const sortedScriptingElapsedTimes = samples
|
||||||
|
.map(({ scriptingStart, scriptingEnd }) => scriptingEnd - scriptingStart)
|
||||||
|
.sort(sortNumbers);
|
||||||
|
const sortedLayoutElapsedTimes = samples
|
||||||
|
.map(({ layoutStart, layoutEnd }) => (layoutEnd || 0) - (layoutStart || 0))
|
||||||
.sort(sortNumbers);
|
.sort(sortNumbers);
|
||||||
const mean = getMean(sortedElapsedTimes);
|
|
||||||
const stdDev = getStdDev(sortedElapsedTimes);
|
|
||||||
|
|
||||||
onComplete({
|
onComplete({
|
||||||
startTime: this._startTime,
|
startTime: this._startTime,
|
||||||
@@ -214,8 +238,10 @@ export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkSt
|
|||||||
max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
|
max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
|
||||||
min: sortedElapsedTimes[0],
|
min: sortedElapsedTimes[0],
|
||||||
median: getMedian(sortedElapsedTimes),
|
median: getMedian(sortedElapsedTimes),
|
||||||
mean,
|
mean: getMean(sortedElapsedTimes),
|
||||||
stdDev
|
stdDev: getStdDev(sortedElapsedTimes),
|
||||||
|
meanLayout: getMean(sortedLayoutElapsedTimes),
|
||||||
|
meanScripting: getMean(sortedScriptingElapsedTimes)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// @flow
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
export type BenchResultsType = {
|
export type BenchResultsType = {
|
||||||
startTime: number,
|
startTime: number,
|
||||||
endTime: number,
|
endTime: number,
|
||||||
@@ -9,20 +11,21 @@ export type BenchResultsType = {
|
|||||||
min: number,
|
min: number,
|
||||||
median: number,
|
median: number,
|
||||||
mean: number,
|
mean: number,
|
||||||
stdDev: number,
|
stdDev: number
|
||||||
p70: number,
|
|
||||||
p95: number,
|
|
||||||
p99: number
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SampleTimingType = {
|
export type SampleTimingType = {
|
||||||
start: number,
|
scriptingStart: number,
|
||||||
end?: number,
|
scriptingEnd?: number,
|
||||||
elapsed?: number
|
layoutStart?: number,
|
||||||
|
layoutEnd?: number
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FullSampleTimingType = {
|
export type FullSampleTimingType = {
|
||||||
start: number,
|
start: number,
|
||||||
end: number,
|
end: number,
|
||||||
elapsed: number
|
scriptingStart: number,
|
||||||
|
scriptingEnd: number,
|
||||||
|
layoutStart?: number,
|
||||||
|
layoutEnd?: number
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ class ReportCard extends React.PureComponent {
|
|||||||
libraryName,
|
libraryName,
|
||||||
sampleCount,
|
sampleCount,
|
||||||
mean,
|
mean,
|
||||||
|
meanLayout,
|
||||||
|
meanScripting,
|
||||||
stdDev,
|
stdDev,
|
||||||
runTime,
|
|
||||||
libraryVersion
|
libraryVersion
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -38,9 +39,8 @@ class ReportCard extends React.PureComponent {
|
|||||||
<Text style={[styles.bold, styles.monoFont]}>
|
<Text style={[styles.bold, styles.monoFont]}>
|
||||||
{fmt(mean)} ±{fmt(stdDev)} ms
|
{fmt(mean)} ±{fmt(stdDev)} ms
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.monoFont, styles.centerText]}>
|
<Text style={[styles.smallText, styles.monoFont]}>
|
||||||
<Text style={styles.smallText}>Σ = </Text>
|
(S/L) {fmt(meanScripting)} / {fmt(meanLayout)} ms
|
||||||
<Text>{Math.round(runTime)} ms</Text>
|
|
||||||
</Text>
|
</Text>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
|
|
||||||
/**
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { bool } from 'prop-types';
|
import { bool } from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* @flow */
|
|
||||||
|
|
||||||
import { type Component } from 'react';
|
import { type Component } from 'react';
|
||||||
import packageJson from '../package.json';
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user