diff --git a/README.md b/README.md index 3b95755..181382d 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,21 @@ dispose(calculateSum) *Hyperactiv websocket implementation.* +## Performance + +This repository includes a [benchmark folder](https://github.com/elbywan/hyperactiv/tree/master/bench) which pits `hyperactiv` against other similar libraries. + +While not the best in terms of raw performance it is still reasonably fast and I encourage you to have a look at the different implementations to compare the library APIs. [For instance there is no `.get()` and `.set()` wrappers when using `hyperactiv`](https://github.com/elbywan/hyperactiv/blob/master/bench/layers.mjs#L302). + +**Here are the raw results: _(50 runs per tiers, average time ignoring the 10 best & 10 worst runs)_** + +![bench](./docs/bench.png) + +> Each tier nests observable objects X (10/100/500/1000…) times and performs some computations on the deepest one. This causes reactions to propagate to the whole observable tree. + + +_**Disclaimer**: I adapted the code from [`maverickjs`](https://github.com/maverick-js/observables/tree/main/bench) which was itself a rewrite of the benchmark from [`cellx`](https://github.com/Riim/cellx#benchmark). I also wrote some MobX code which might not be the best in terms of optimization since I am not very familiar with the API._ + ## Code samples #### A simple sum and a counter diff --git a/bench/layers.mjs b/bench/layers.mjs index 8d068c1..47cc8be 100644 --- a/bench/layers.mjs +++ b/bench/layers.mjs @@ -7,14 +7,15 @@ import kleur from 'kleur' import * as cellx from 'cellx' - import * as Sjs from 's-js' import * as mobx from 'mobx' import * as maverick from '@maverick-js/observables' import hyperactiv from 'hyperactiv' import Table from 'cli-table' +import pkgs from './pkgs.cjs' -const RUNS_PER_TIER = 25 +const RUNS_PER_TIER = 50 +const DISCARD_BEST_WORST_X_RUNS = 10 const LAYER_TIERS = [10, 100, 500, 1000, 2000, 2500, 5000, 10000] const sum = array => array.reduce((a, b) => a + b, 0) @@ -37,13 +38,15 @@ const SOLUTIONS = { */ const isSolution = (layers, answer) => answer.every((s, i) => s === SOLUTIONS[layers][i]) +const pkgKey = pkg => `${pkgs[pkg].name}@${pkgs[pkg].version}` + async function main() { const report = { - cellx: { fn: runCellx, runs: [] }, - hyperactiv: { fn: runHyperactiv, runs: [] }, - maverick: { fn: runMaverick, runs: [], avg: [] }, - mobx: { fn: runMobx, runs: [] }, - S: { fn: runS, runs: [] } + [pkgKey('cellx')]: { fn: runCellx, runs: [] }, + [pkgKey('hyperactiv')]: { fn: runHyperactiv, runs: [] }, + [pkgKey('maverick')]: { fn: runMaverick, runs: [], avg: [] }, + [pkgKey('mobx')]: { fn: runMobx, runs: [] }, + [pkgKey('S')]: { fn: runS, runs: [] } } for(const lib of Object.keys(report)) { @@ -51,7 +54,7 @@ async function main() { for(let i = 0; i < LAYER_TIERS.length; i += 1) { const layers = LAYER_TIERS[i] - const runs = [] + let runs = [] let result = null for(let j = 0; j < RUNS_PER_TIER; j += 1) { @@ -64,6 +67,9 @@ async function main() { if(typeof result !== 'number') { current.runs[i] = result } else { + if(DISCARD_BEST_WORST_X_RUNS) { + runs = runs.sort().slice(DISCARD_BEST_WORST_X_RUNS, -DISCARD_BEST_WORST_X_RUNS) + } current.runs[i] = avg(runs) * 1000 } } @@ -119,7 +125,7 @@ async function start(runner, layers) { runner(layers, done) }).catch(error => { console.error(error) - return 'error' + return error.message.toString() }) } @@ -262,12 +268,12 @@ function runMobx(layers, done) { for(let i = layers; i--;) { layer = (prev => { - const next = mobx.observable({ + const next = { a: mobx.computed(() => prev.b.get()), b: mobx.computed(() => prev.a.get() - prev.c.get()), c: mobx.computed(() => prev.b.get() + prev.d.get()), d: mobx.computed(() => prev.c.get()) - }) + } return next })(layer) @@ -294,21 +300,24 @@ function runMobx(layers, done) { } function runHyperactiv(layers, done) { - const start = hyperactiv.observe({ + const observe = obj => hyperactiv.observe(obj, { batch: true }) + const computed = fn => hyperactiv.computed(fn, { disableTracking: true }) + + const start = observe({ a: 1, b: 2, c: 3, d: 4 - }, { batch: true }) + }) let layer = start for(let i = layers; i--;) { layer = (prev => { - const next = hyperactiv.observe({}, { batch: true }) - hyperactiv.computed(() => next.a = prev.b, { disableTracking: true }) - hyperactiv.computed(() => next.b = prev.a - prev.c, { disableTracking: true }) - hyperactiv.computed(() => next.c = prev.b + prev.d, { disableTracking: true }) - hyperactiv.computed(() => next.d = prev.c, { disableTracking: true }) + const next = observe({}) + computed(() => next.a = prev.b) + computed(() => next.b = prev.a - prev.c) + computed(() => next.c = prev.b + prev.d) + computed(() => next.d = prev.c) return next })(layer) } diff --git a/bench/package-lock.json b/bench/package-lock.json index dd25adf..bddbf0d 100644 --- a/bench/package-lock.json +++ b/bench/package-lock.json @@ -18,7 +18,7 @@ } }, "..": { - "version": "0.10.3", + "version": "0.11.1", "license": "MIT", "devDependencies": { "@babel/core": "^7.17.10", diff --git a/bench/pkgs.cjs b/bench/pkgs.cjs new file mode 100644 index 0000000..d91d601 --- /dev/null +++ b/bench/pkgs.cjs @@ -0,0 +1,7 @@ +module.exports = { + cellx: require('cellx/package.json'), + hyperactiv: require('hyperactiv/package.json'), + maverick: require('@maverick-js/observables/package.json'), + mobx: require('mobx/package.json'), + S: require('s-js/package.json') +} \ No newline at end of file diff --git a/docs/bench.png b/docs/bench.png new file mode 100644 index 0000000..5469c83 Binary files /dev/null and b/docs/bench.png differ