Skip to content

Commit cf20790

Browse files
committed
more reliable benchmark with memory stats
1 parent 7ba611c commit cf20790

1 file changed

Lines changed: 88 additions & 23 deletions

File tree

bench.js

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,100 @@
1+
// Memory + speed benchmark for supercluster.
2+
//
3+
// Run: node --expose-gc bench.js
4+
//
5+
// Metrics:
6+
// ms median build time
7+
// alloc held + Σ(bytes freed by GC during build), via v8.GCProfiler
8+
// peak max usedHeapSize before any in-build GC
9+
// held heapUsed + external after multi-pass forced GC, vs baseline
10+
//
11+
// Each iteration's build runs inside buildAndMeasure() so the local `idx`
12+
// goes away with the call frame on return. Without this, V8 keeps the
13+
// previous iteration's index alive on the loop body's stack frame and
14+
// `held` measures incorrectly (often near zero or negative).
115

2-
import Supercluster from './index.js';
316
import v8 from 'v8';
17+
import Supercluster from './index.js';
18+
19+
if (!global.gc) { console.error('run with --expose-gc'); process.exit(2); }
20+
21+
const N = 1_000_000;
22+
const ITER = 3;
23+
const OPTS = {log: true, maxZoom: 17};
424

525
const points = [];
6-
for (let i = 0; i < 1000000; i++) {
26+
for (let i = 0; i < N; i++) {
727
points.push({
828
type: 'Feature',
9-
properties: {
10-
index: i
11-
},
12-
geometry: {
13-
type: 'Point',
14-
coordinates: [
15-
-180 + 360 * Math.random(),
16-
-80 + 160 * Math.random()
17-
]
18-
}
29+
properties: {index: i},
30+
geometry: {type: 'Point', coordinates: [
31+
-180 + 360 * Math.random(),
32+
-80 + 160 * Math.random()
33+
]}
1934
});
2035
}
2136

22-
global.gc();
23-
const size = v8.getHeapStatistics().used_heap_size;
37+
function settle() {
38+
for (let i = 0; i < 8; i++) global.gc();
39+
}
40+
41+
function snap() {
42+
const m = process.memoryUsage();
43+
return m.heapUsed + m.external;
44+
}
2445

25-
const index = new Supercluster({
26-
log: true,
27-
maxZoom: 6,
28-
// map: props => ({sum: props.index}),
29-
// reduce: (accumulated, props) => { accumulated.sum += props.sum; }
30-
}).load(points);
46+
function fmt(b) {
47+
const mb = Math.abs(b) / (1024 * 1024);
48+
if (mb < 1) return `${(b / 1024).toFixed(0)} KB`;
49+
if (mb < 100) return `${(b / (1024 * 1024)).toFixed(1)} MB`;
50+
return `${(b / (1024 * 1024)).toFixed(0)} MB`;
51+
}
52+
53+
function buildAndMeasure(baseline) {
54+
const prof = new v8.GCProfiler();
55+
prof.start();
56+
const t0 = performance.now();
57+
const idx = new Supercluster(OPTS).load(points);
58+
const ms = performance.now() - t0;
59+
const stats = prof.stop().statistics;
60+
const ext = process.memoryUsage().external;
61+
62+
let freed = 0;
63+
let peakHeap = 0;
64+
for (const e of stats) {
65+
const before = e.beforeGC.heapStatistics.usedHeapSize;
66+
const after = e.afterGC.heapStatistics.usedHeapSize;
67+
if (before > after) freed += before - after;
68+
if (before > peakHeap) peakHeap = before;
69+
}
70+
71+
settle();
72+
const held = snap() - baseline;
73+
// feed a read of idx into the returned value; V8 can't elide a property
74+
// load whose result escapes, so idx stays live across settle()
75+
return {ms, alloc: held + freed, peak: peakHeap + ext - baseline, held, trees: idx.trees.length};
76+
}
77+
78+
console.log(`--- warmup (${N.toLocaleString()} points, maxZoom ${OPTS.maxZoom}) ---`);
79+
(function warmup() { new Supercluster(OPTS).load(points); })();
80+
settle();
81+
const baseline = snap();
82+
83+
const samples = [];
84+
for (let i = 0; i < ITER; i++) {
85+
console.log(`\n--- iteration ${i + 1} of ${ITER} ---`);
86+
const s = buildAndMeasure(baseline);
87+
settle();
88+
console.log(`alloc=${fmt(s.alloc)} peak=${fmt(s.peak)} held=${fmt(s.held)}`);
89+
samples.push(s);
90+
}
3191

32-
global.gc();
33-
console.log(`memory used: ${Math.round((v8.getHeapStatistics().used_heap_size - size) / 1024)} KB`);
92+
const pick = (key, agg) => agg(samples.map(s => s[key]));
93+
const max = arr => Math.max(...arr);
94+
const median = arr => arr.sort((a, b) => a - b)[arr.length >> 1];
3495

35-
index.getClusters([-180, -90, 180, 90], 0).map(f => JSON.stringify(f.properties));
96+
console.log(`\n--- results (median ms/held, max alloc/peak across ${ITER} iterations) ---`);
97+
console.log(`ms: ${pick('ms', median).toFixed(0)}`);
98+
console.log(`alloc: ${fmt(pick('alloc', max))}`);
99+
console.log(`peak: ${fmt(pick('peak', max))}`);
100+
console.log(`held: ${fmt(pick('held', median))}`);

0 commit comments

Comments
 (0)