mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-26 13:35:24 +08: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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* @flow */
|
||||
|
||||
import { type Component } from 'react';
|
||||
import packageJson from '../package.json';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user