Add initial performance benchmarks

Simple render tree benchmarks originally developed by @lelandrichardson

Fix #306
This commit is contained in:
Nicolas Gallagher
2017-01-01 14:40:44 -08:00
parent 351c0ac3d4
commit a2cafe56fc
11 changed files with 256 additions and 3 deletions
+1
View File
@@ -13,5 +13,6 @@ https://github.com/necolas/react-native-web/CONTRIBUTING.md
- [ ] includes documentation
- [ ] includes tests
- [ ] includes benchmark reports
- [ ] includes an interactive example
- [ ] includes screenshots/videos
+1
View File
@@ -1,3 +1,4 @@
/dist
/dist-examples
/dist-performance
/node_modules
+6 -2
View File
@@ -1,7 +1,5 @@
# Contributing
We are open to, and grateful for, any contributions made by the community.
## Reporting Issues and Asking Questions
Before opening an issue, please search the [issue
@@ -31,6 +29,12 @@ Run the examples:
npm run examples
```
Run the benchmarks in a browser by opening `./performance/index.html` after running:
```
npm run build:performance
```
### Building
```
+3 -1
View File
@@ -11,10 +11,11 @@
"scripts": {
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
"build:performance": "cd performance && webpack",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
"lint": "eslint src",
"lint": "eslint performance src",
"prepublish": "npm run build && npm run build:umd",
"test": "npm run lint && npm run test:jest",
"test:jest": "jest",
@@ -48,6 +49,7 @@
"eslint-plugin-react": "^6.1.2",
"file-loader": "^0.9.0",
"jest": "^16.0.2",
"marky": "^1.1.1",
"node-libs-browser": "^0.5.3",
"react": "~15.4.1",
"react-addons-test-utils": "~15.4.1",
+65
View File
@@ -0,0 +1,65 @@
import * as marky from 'marky';
const fmt = (time) => `${Math.round(time * 100) / 100}ms`;
const measure = (name, fn) => {
marky.mark(name);
fn();
const performanceMeasure = marky.stop(name);
return performanceMeasure;
};
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
return new Promise((resolve) => {
const performanceMeasures = [];
let i = 0;
setup();
const first = measure('first', task);
teardown();
const done = () => {
const mean = performanceMeasures.reduce((sum, performanceMeasure) => {
return sum + performanceMeasure.duration;
}, 0) / runs;
const firstDuration = fmt(first.duration);
const meanDuration = fmt(mean);
console.log(`First: ${firstDuration}`);
console.log(`Mean: ${meanDuration}`);
console.groupEnd();
resolve(mean);
};
const a = () => {
setup();
window.requestAnimationFrame(b);
};
const b = () => {
performanceMeasures.push(measure('mean', task));
window.requestAnimationFrame(c);
};
const c = () => {
teardown();
window.requestAnimationFrame(d);
};
const d = () => {
i += 1;
if (i < runs) {
window.requestAnimationFrame(a);
} else {
window.requestAnimationFrame(done);
}
};
console.group();
console.log(`[benchmark] ${name}: ${description}`);
window.requestAnimationFrame(a);
});
};
module.exports = benchmark;
@@ -0,0 +1,85 @@
import React, { Component, PropTypes } from 'react';
const createDeepTree = ({ StyleSheet, View }) => {
class DeepTree extends Component {
static propTypes = {
breadth: PropTypes.number.isRequired,
depth: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
wrap: PropTypes.number.isRequired
};
render() {
const { breadth, depth, id, wrap } = this.props;
let result = (
<View
style={[
styles.outer,
depth % 2 === 0 ? styles.even : styles.odd,
styles[`custom${id % 3}`]
]}
>
{depth === 0 && (
<View
style={[
styles.terminal,
styles[`terminal${id % 3}`]
]}
/>
)}
{depth !== 0 && Array.from({ length: breadth }).map((el, i) => (
<DeepTree
breadth={breadth}
depth={depth - 1}
id={i}
key={i}
wrap={wrap}
/>
))}
</View>
);
for (let i = 0; i < wrap; i++) {
result = <View>{result}</View>;
}
return result;
}
}
const styles = StyleSheet.create({
outer: {
padding: 4
},
odd: {
flexDirection: 'row'
},
even: {
flexDirection: 'column'
},
custom0: {
backgroundColor: '#222'
},
custom1: {
backgroundColor: '#666'
},
custom2: {
backgroundColor: '#999'
},
terminal: {
width: 20,
height: 20
},
terminal0: {
backgroundColor: 'blue'
},
terminal1: {
backgroundColor: 'orange'
},
terminal2: {
backgroundColor: 'red'
}
});
return DeepTree;
};
module.exports = createDeepTree;
+24
View File
@@ -0,0 +1,24 @@
import benchmark from '../../benchmark';
import createDeepTree from './createDeepTree';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactNative from 'react-native';
// React Native for Web implementation of the tree
const DeepTree = createDeepTree(ReactNative);
const deepTreeBenchmark = ({ breadth, depth, wrap, runs }, node) => () => {
const setup = () => { };
const teardown = () => ReactDOM.unmountComponentAtNode(node);
return benchmark({
name: 'DeepTree',
description: `depth=${depth}, breadth=${breadth}, wrap=${wrap})`,
runs,
setup,
teardown,
task: () => ReactDOM.render(<DeepTree breadth={breadth} depth={depth} id={0} wrap={wrap} />, node)
});
};
module.exports = deepTreeBenchmark;
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="root"></div>
<script src="../dist-performance/performance.bundle.js"></script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
import deepTree from './benchmarks/deepTree';
import React from 'react';
import ReactDOM from 'react-dom';
const node = document.querySelector('.root');
Promise.resolve()
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10 }, node))
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20 }, node))
.then(() => ReactDOM.render(<div>Complete</div>, node));
+45
View File
@@ -0,0 +1,45 @@
const path = require('path');
const webpack = require('webpack');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
performance: './index'
},
output: {
path: path.resolve(__dirname, '../dist-performance'),
filename: 'performance.bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
plugins: [
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.optimize.DedupePlugin(),
// https://github.com/animatedjs/animated/issues/40
new webpack.NormalModuleReplacementPlugin(
/es6-set/,
path.join(__dirname, '../src/modules/polyfills/Set.js')
),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
screw_ie8: true,
warnings: true
}
})
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../src')
}
}
};
+4
View File
@@ -3738,6 +3738,10 @@ marked@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
marky@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/marky/-/marky-1.1.1.tgz#dcd1eba3e2c6412619a76981f635b3698a8761e8"
math-expression-evaluator@^1.2.14:
version "1.2.14"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz#39511771ed9602405fba9affff17eb4d2a3843ab"