Skip to content

Commit e7fad70

Browse files
authored
Benchmark runner updates (#189)
* Run benchmarks against a known previous state ([email protected]) * Add d8-based benchmark runner * fix tests
1 parent d5199c3 commit e7fad70

File tree

5 files changed

+356
-26
lines changed

5 files changed

+356
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/npm-debug.log
44
.DS_Store
55
/src/preact-render-to-string-tests.d.ts
6+
/benchmarks/.v8.modern.js

benchmarks/index.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { h } from 'preact';
22
import Suite from 'benchmarkjs-pretty';
3+
import renderToStringBaseline from './lib/render-to-string';
34
import renderToString from '../src/index';
45
import TextApp from './text';
56
// import StackApp from './stack';
67
import { App as IsomorphicSearchResults } from './isomorphic-ui-search-results';
78

8-
new Suite('Bench')
9-
.add('Text', () => {
10-
return renderToString(<TextApp />);
11-
})
12-
.add('SearchResults', () => {
13-
return renderToString(<IsomorphicSearchResults />);
14-
})
9+
function suite(name, Root) {
10+
return new Suite(name)
11+
.add('baseline', () => renderToStringBaseline(<Root />))
12+
.add('current', () => renderToString(<Root />))
13+
.run();
14+
}
15+
16+
(async () => {
17+
await suite('Text', TextApp);
18+
await suite('SearchResults', IsomorphicSearchResults);
1519
// TODO: Enable this once we switched away from recursion
16-
// .add('Stack Depth', () => {
17-
// return renderToString(<StackApp />);
18-
// })
19-
.run();
20+
// await suite('Stack Depth', StackApp);
21+
})();

benchmarks/lib/benchmark-lite.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
export default class Suite {
2+
constructor(name, { iterations = 10, timeLimit = 5000 } = {}) {
3+
this.name = name;
4+
this.iterations = iterations;
5+
this.timeLimit = timeLimit;
6+
this.tests = [];
7+
}
8+
add(name, executor) {
9+
this.tests.push({ name, executor });
10+
return this;
11+
}
12+
run() {
13+
console.log(` ${this.name}:`);
14+
const results = [];
15+
let fastest = 0;
16+
for (const test of this.tests) {
17+
for (let i = 0; i < 5; i++) test.executor(i);
18+
const result = this.runOne(test);
19+
if (result.hz > fastest) fastest = result.hz;
20+
results.push({ ...test, ...result });
21+
}
22+
const winners = results.filter((r) => r.hz === fastest).map((r) => r.name);
23+
console.log(` Fastest: \x1b[32m${winners}\x1b[39m\n`);
24+
return this;
25+
}
26+
runOne({ name, executor }) {
27+
const { iterations, timeLimit } = this;
28+
let count = 5;
29+
let now = performance.now(),
30+
start = now,
31+
prev = now;
32+
const times = [];
33+
do {
34+
for (let i = iterations; i--; ) executor(++count);
35+
prev = now;
36+
now = performance.now();
37+
times.push((now - prev) / iterations);
38+
} while (now - start < timeLimit);
39+
const elapsed = now - start;
40+
const hz = Math.round((count / elapsed) * 1000);
41+
const average = toFixed(elapsed / count);
42+
const middle = Math.floor(times.length / 2);
43+
const middle2 = Math.ceil(times.length / 2);
44+
times.sort((a, b) => a - b);
45+
const median = toFixed((times[middle] + times[middle2]) / 2);
46+
const hzStr = hz.toLocaleString();
47+
const averageStr = average.toLocaleString();
48+
const n = times.length;
49+
const stdev = Math.sqrt(
50+
times.reduce((c, t) => c + (t - average) ** 2, 0) / (n - 1)
51+
);
52+
const p95 = toFixed((1.96 * stdev) / Math.sqrt(n));
53+
console.log(
54+
` \x1b[36m${name}:\x1b[0m ${hzStr}/s, average: ${averageStr}ms ±${p95} (median: ${median}ms)`
55+
);
56+
return { elapsed, hz, average, median };
57+
}
58+
}
59+
60+
const toFixed = (v) => ((v * 100) | 0) / 100;

benchmarks/lib/render-to-string.js

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/* eslint-disable */
2+
3+
import { options as e, createElement as t, Fragment as r } from 'preact';
4+
var n = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i,
5+
o = /[&<>"]/g,
6+
i = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' },
7+
a = function (e) {
8+
return i[e] || e;
9+
};
10+
function l(e) {
11+
return 'string' != typeof e && (e = String(e)), e.replace(o, a);
12+
}
13+
var s = function (e, t) {
14+
return String(e).replace(/(\n+)/g, '$1' + (t || '\t'));
15+
},
16+
f = function (e, t, r) {
17+
return (
18+
String(e).length > (t || 40) ||
19+
(!r && -1 !== String(e).indexOf('\n')) ||
20+
-1 !== String(e).indexOf('<')
21+
);
22+
},
23+
u = {};
24+
function c(e) {
25+
var t = '';
26+
for (var r in e) {
27+
var o = e[r];
28+
null != o &&
29+
'' !== o &&
30+
(t && (t += ' '),
31+
(t +=
32+
'-' == r[0]
33+
? r
34+
: u[r] || (u[r] = r.replace(/([A-Z])/g, '-$1').toLowerCase())),
35+
(t += ': '),
36+
(t += o),
37+
'number' == typeof o && !1 === n.test(r) && (t += 'px'),
38+
(t += ';'));
39+
}
40+
return t || void 0;
41+
}
42+
function p(e, t) {
43+
for (var r in t) e[r] = t[r];
44+
return e;
45+
}
46+
function _(e, t) {
47+
return (
48+
Array.isArray(t) ? t.reduce(_, e) : null != t && !1 !== t && e.push(t), e
49+
);
50+
}
51+
var v = { shallow: !0 },
52+
d = [],
53+
g = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/,
54+
h = /[\s\n\\/='"\0<>]/,
55+
m = function () {};
56+
b.render = b;
57+
var x = function (e, t) {
58+
return b(e, t, v);
59+
},
60+
y = [];
61+
function b(t, r, n) {
62+
var o = S(t, r, n);
63+
return e.__c && e.__c(t, y), o;
64+
}
65+
function S(n, o, i, a, u, v) {
66+
if (null == n || 'boolean' == typeof n) return '';
67+
Array.isArray(n) && (n = t(r, null, n));
68+
var x = n.type,
69+
y = n.props,
70+
b = !1;
71+
o = o || {};
72+
var w,
73+
k = (i = i || {}).pretty,
74+
O = k && 'string' == typeof k ? k : '\t';
75+
if ('object' != typeof n && !x) return l(n);
76+
if ('function' == typeof x) {
77+
if (((b = !0), !i.shallow || (!a && !1 !== i.renderRootComponent))) {
78+
if (x === r) {
79+
var C = '',
80+
A = [];
81+
_(A, n.props.children);
82+
for (var H = 0; H < A.length; H++)
83+
C +=
84+
(H > 0 && k ? '\n' : '') +
85+
S(A[H], o, i, !1 !== i.shallowHighOrder, u, v);
86+
return C;
87+
}
88+
var j,
89+
F = (n.__c = {
90+
__v: n,
91+
context: o,
92+
props: n.props,
93+
setState: m,
94+
forceUpdate: m,
95+
__h: []
96+
});
97+
if (
98+
(e.__b && e.__b(n),
99+
e.__r && e.__r(n),
100+
x.prototype && 'function' == typeof x.prototype.render)
101+
) {
102+
var M = x.contextType,
103+
T = M && o[M.__c],
104+
$ = null != M ? (T ? T.props.value : M.__) : o;
105+
((F = n.__c = new x(y, $)).__v = n),
106+
(F._dirty = F.__d = !0),
107+
(F.props = y),
108+
null == F.state && (F.state = {}),
109+
null == F._nextState &&
110+
null == F.__s &&
111+
(F._nextState = F.__s = F.state),
112+
(F.context = $),
113+
x.getDerivedStateFromProps
114+
? (F.state = p(
115+
p({}, F.state),
116+
x.getDerivedStateFromProps(F.props, F.state)
117+
))
118+
: F.componentWillMount &&
119+
(F.componentWillMount(),
120+
(F.state =
121+
F._nextState !== F.state
122+
? F._nextState
123+
: F.__s !== F.state
124+
? F.__s
125+
: F.state)),
126+
(j = F.render(F.props, F.state, F.context));
127+
} else {
128+
var L = x.contextType,
129+
E = L && o[L.__c];
130+
j = x.call(n.__c, y, null != L ? (E ? E.props.value : L.__) : o);
131+
}
132+
return (
133+
F.getChildContext && (o = p(p({}, o), F.getChildContext())),
134+
e.diffed && e.diffed(n),
135+
S(j, o, i, !1 !== i.shallowHighOrder, u, v)
136+
);
137+
}
138+
x =
139+
(w = x).displayName ||
140+
(w !== Function && w.name) ||
141+
(function (e) {
142+
var t = (Function.prototype.toString
143+
.call(e)
144+
.match(/^\s*function\s+([^( ]+)/) || '')[1];
145+
if (!t) {
146+
for (var r = -1, n = d.length; n--; )
147+
if (d[n] === e) {
148+
r = n;
149+
break;
150+
}
151+
r < 0 && (r = d.push(e) - 1), (t = 'UnnamedComponent' + r);
152+
}
153+
return t;
154+
})(w);
155+
}
156+
var D,
157+
N,
158+
P = '';
159+
if (y) {
160+
var R = Object.keys(y);
161+
i && !0 === i.sortAttributes && R.sort();
162+
for (var U = 0; U < R.length; U++) {
163+
var W = R[U],
164+
q = y[W];
165+
if ('children' !== W) {
166+
if (
167+
!W.match(/[\s\n\\/='"\0<>]/) &&
168+
((i && i.allAttributes) ||
169+
('key' !== W &&
170+
'ref' !== W &&
171+
'__self' !== W &&
172+
'__source' !== W &&
173+
'defaultValue' !== W))
174+
) {
175+
if ('className' === W) {
176+
if (y.class) continue;
177+
W = 'class';
178+
} else
179+
u &&
180+
W.match(/^xlink:?./) &&
181+
(W = W.toLowerCase().replace(/^xlink:?/, 'xlink:'));
182+
if ('htmlFor' === W) {
183+
if (y.for) continue;
184+
W = 'for';
185+
}
186+
'style' === W && q && 'object' == typeof q && (q = c(q)),
187+
'a' === W[0] &&
188+
'r' === W[1] &&
189+
'boolean' == typeof q &&
190+
(q = String(q));
191+
var z = i.attributeHook && i.attributeHook(W, q, o, i, b);
192+
if (z || '' === z) P += z;
193+
else if ('dangerouslySetInnerHTML' === W) N = q && q.__html;
194+
else if ('textarea' === x && 'value' === W) D = q;
195+
else if ((q || 0 === q || '' === q) && 'function' != typeof q) {
196+
if (!((!0 !== q && '' !== q) || ((q = W), i && i.xml))) {
197+
P += ' ' + W;
198+
continue;
199+
}
200+
if ('value' === W) {
201+
if ('select' === x) {
202+
v = q;
203+
continue;
204+
}
205+
'option' === x && v == q && (P += ' selected');
206+
}
207+
P += ' ' + W + '="' + l(q) + '"';
208+
}
209+
}
210+
} else D = q;
211+
}
212+
}
213+
if (k) {
214+
var I = P.replace(/^\n\s*/, ' ');
215+
I === P || ~I.indexOf('\n')
216+
? k && ~P.indexOf('\n') && (P += '\n')
217+
: (P = I);
218+
}
219+
if (((P = '<' + x + P + '>'), h.test(String(x))))
220+
throw new Error(x + ' is not a valid HTML tag name in ' + P);
221+
var V,
222+
Z = g.test(String(x)) || (i.voidElements && i.voidElements.test(String(x))),
223+
B = [];
224+
if (N) k && f(N) && (N = '\n' + O + s(N, O)), (P += N);
225+
else if (null != D && _((V = []), D).length) {
226+
for (var G = k && ~P.indexOf('\n'), J = !1, K = 0; K < V.length; K++) {
227+
var Q = V[K];
228+
if (null != Q && !1 !== Q) {
229+
var X = S(Q, o, i, !0, 'svg' === x || ('foreignObject' !== x && u), v);
230+
if ((k && !G && f(X) && (G = !0), X))
231+
if (k) {
232+
var Y = X.length > 0 && '<' != X[0];
233+
J && Y ? (B[B.length - 1] += X) : B.push(X), (J = Y);
234+
} else B.push(X);
235+
}
236+
}
237+
if (k && G) for (var ee = B.length; ee--; ) B[ee] = '\n' + O + s(B[ee], O);
238+
}
239+
if (B.length || N) P += B.join('');
240+
else if (i && i.xml) return P.substring(0, P.length - 1) + ' />';
241+
return (
242+
!Z || V || N
243+
? (k && ~P.indexOf('\n') && (P += '\n'), (P += '</' + x + '>'))
244+
: (P = P.replace(/>$/, ' />')),
245+
P
246+
);
247+
}
248+
b.shallowRender = x;
249+
export default b;
250+
export {
251+
b as render,
252+
b as renderToStaticMarkup,
253+
b as renderToString,
254+
x as shallowRender
255+
};

0 commit comments

Comments
 (0)