Skip to content

Commit a2cafe5

Browse files
committed
Add initial performance benchmarks
Simple render tree benchmarks originally developed by @lelandrichardson Fix #306
1 parent 351c0ac commit a2cafe5

File tree

11 files changed

+256
-3
lines changed

11 files changed

+256
-3
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ https://github.com/necolas/react-native-web/CONTRIBUTING.md
1313

1414
- [ ] includes documentation
1515
- [ ] includes tests
16+
- [ ] includes benchmark reports
1617
- [ ] includes an interactive example
1718
- [ ] includes screenshots/videos

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/dist
22
/dist-examples
3+
/dist-performance
34
/node_modules

CONTRIBUTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Contributing
22

3-
We are open to, and grateful for, any contributions made by the community.
4-
53
## Reporting Issues and Asking Questions
64

75
Before opening an issue, please search the [issue
@@ -31,6 +29,12 @@ Run the examples:
3129
npm run examples
3230
```
3331

32+
Run the benchmarks in a browser by opening `./performance/index.html` after running:
33+
34+
```
35+
npm run build:performance
36+
```
37+
3438
### Building
3539

3640
```

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
"scripts": {
1212
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
1313
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
14+
"build:performance": "cd performance && webpack",
1415
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
1516
"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 -",
1617
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
17-
"lint": "eslint src",
18+
"lint": "eslint performance src",
1819
"prepublish": "npm run build && npm run build:umd",
1920
"test": "npm run lint && npm run test:jest",
2021
"test:jest": "jest",
@@ -48,6 +49,7 @@
4849
"eslint-plugin-react": "^6.1.2",
4950
"file-loader": "^0.9.0",
5051
"jest": "^16.0.2",
52+
"marky": "^1.1.1",
5153
"node-libs-browser": "^0.5.3",
5254
"react": "~15.4.1",
5355
"react-addons-test-utils": "~15.4.1",

performance/benchmark.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as marky from 'marky';
2+
3+
const fmt = (time) => `${Math.round(time * 100) / 100}ms`;
4+
5+
const measure = (name, fn) => {
6+
marky.mark(name);
7+
fn();
8+
const performanceMeasure = marky.stop(name);
9+
return performanceMeasure;
10+
};
11+
12+
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
13+
return new Promise((resolve) => {
14+
const performanceMeasures = [];
15+
let i = 0;
16+
17+
setup();
18+
const first = measure('first', task);
19+
teardown();
20+
21+
const done = () => {
22+
const mean = performanceMeasures.reduce((sum, performanceMeasure) => {
23+
return sum + performanceMeasure.duration;
24+
}, 0) / runs;
25+
26+
const firstDuration = fmt(first.duration);
27+
const meanDuration = fmt(mean);
28+
29+
console.log(`First: ${firstDuration}`);
30+
console.log(`Mean: ${meanDuration}`);
31+
console.groupEnd();
32+
resolve(mean);
33+
};
34+
35+
const a = () => {
36+
setup();
37+
window.requestAnimationFrame(b);
38+
};
39+
40+
const b = () => {
41+
performanceMeasures.push(measure('mean', task));
42+
window.requestAnimationFrame(c);
43+
};
44+
45+
const c = () => {
46+
teardown();
47+
window.requestAnimationFrame(d);
48+
};
49+
50+
const d = () => {
51+
i += 1;
52+
if (i < runs) {
53+
window.requestAnimationFrame(a);
54+
} else {
55+
window.requestAnimationFrame(done);
56+
}
57+
};
58+
59+
console.group();
60+
console.log(`[benchmark] ${name}: ${description}`);
61+
window.requestAnimationFrame(a);
62+
});
63+
};
64+
65+
module.exports = benchmark;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { Component, PropTypes } from 'react';
2+
3+
const createDeepTree = ({ StyleSheet, View }) => {
4+
class DeepTree extends Component {
5+
static propTypes = {
6+
breadth: PropTypes.number.isRequired,
7+
depth: PropTypes.number.isRequired,
8+
id: PropTypes.number.isRequired,
9+
wrap: PropTypes.number.isRequired
10+
};
11+
12+
render() {
13+
const { breadth, depth, id, wrap } = this.props;
14+
let result = (
15+
<View
16+
style={[
17+
styles.outer,
18+
depth % 2 === 0 ? styles.even : styles.odd,
19+
styles[`custom${id % 3}`]
20+
]}
21+
>
22+
{depth === 0 && (
23+
<View
24+
style={[
25+
styles.terminal,
26+
styles[`terminal${id % 3}`]
27+
]}
28+
/>
29+
)}
30+
{depth !== 0 && Array.from({ length: breadth }).map((el, i) => (
31+
<DeepTree
32+
breadth={breadth}
33+
depth={depth - 1}
34+
id={i}
35+
key={i}
36+
wrap={wrap}
37+
/>
38+
))}
39+
</View>
40+
);
41+
for (let i = 0; i < wrap; i++) {
42+
result = <View>{result}</View>;
43+
}
44+
return result;
45+
}
46+
}
47+
48+
const styles = StyleSheet.create({
49+
outer: {
50+
padding: 4
51+
},
52+
odd: {
53+
flexDirection: 'row'
54+
},
55+
even: {
56+
flexDirection: 'column'
57+
},
58+
custom0: {
59+
backgroundColor: '#222'
60+
},
61+
custom1: {
62+
backgroundColor: '#666'
63+
},
64+
custom2: {
65+
backgroundColor: '#999'
66+
},
67+
terminal: {
68+
width: 20,
69+
height: 20
70+
},
71+
terminal0: {
72+
backgroundColor: 'blue'
73+
},
74+
terminal1: {
75+
backgroundColor: 'orange'
76+
},
77+
terminal2: {
78+
backgroundColor: 'red'
79+
}
80+
});
81+
82+
return DeepTree;
83+
};
84+
85+
module.exports = createDeepTree;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import benchmark from '../../benchmark';
2+
import createDeepTree from './createDeepTree';
3+
import React from 'react';
4+
import ReactDOM from 'react-dom';
5+
import ReactNative from 'react-native';
6+
7+
// React Native for Web implementation of the tree
8+
const DeepTree = createDeepTree(ReactNative);
9+
10+
const deepTreeBenchmark = ({ breadth, depth, wrap, runs }, node) => () => {
11+
const setup = () => { };
12+
const teardown = () => ReactDOM.unmountComponentAtNode(node);
13+
14+
return benchmark({
15+
name: 'DeepTree',
16+
description: `depth=${depth}, breadth=${breadth}, wrap=${wrap})`,
17+
runs,
18+
setup,
19+
teardown,
20+
task: () => ReactDOM.render(<DeepTree breadth={breadth} depth={depth} id={0} wrap={wrap} />, node)
21+
});
22+
};
23+
24+
module.exports = deepTreeBenchmark;

performance/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
<div class="root"></div>
9+
<script src="../dist-performance/performance.bundle.js"></script>
10+
</body>
11+
</html>

performance/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import deepTree from './benchmarks/deepTree';
2+
import React from 'react';
3+
import ReactDOM from 'react-dom';
4+
5+
const node = document.querySelector('.root');
6+
7+
Promise.resolve()
8+
.then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10 }, node))
9+
.then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20 }, node))
10+
.then(() => ReactDOM.render(<div>Complete</div>, node));
11+

performance/webpack.config.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const path = require('path');
2+
const webpack = require('webpack');
3+
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4+
5+
module.exports = {
6+
entry: {
7+
performance: './index'
8+
},
9+
output: {
10+
path: path.resolve(__dirname, '../dist-performance'),
11+
filename: 'performance.bundle.js'
12+
},
13+
module: {
14+
loaders: [
15+
{
16+
test: /\.js$/,
17+
exclude: /node_modules/,
18+
loader: 'babel-loader',
19+
query: { cacheDirectory: true }
20+
}
21+
]
22+
},
23+
plugins: [
24+
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
25+
new webpack.optimize.DedupePlugin(),
26+
// https://github.com/animatedjs/animated/issues/40
27+
new webpack.NormalModuleReplacementPlugin(
28+
/es6-set/,
29+
path.join(__dirname, '../src/modules/polyfills/Set.js')
30+
),
31+
new webpack.optimize.OccurenceOrderPlugin(),
32+
new webpack.optimize.UglifyJsPlugin({
33+
compress: {
34+
dead_code: true,
35+
screw_ie8: true,
36+
warnings: true
37+
}
38+
})
39+
],
40+
resolve: {
41+
alias: {
42+
'react-native': path.join(__dirname, '../src')
43+
}
44+
}
45+
};

0 commit comments

Comments
 (0)