objix
Version:
A powerfull, high performance, lightweight library to injecs many usefull methods into the Object prototype to sugar many use cases working with common Javascript object.
129 lines (124 loc) • 4.87 kB
JavaScript
const reflect = require('reflect-metadata')
const assert = require('node:assert')
require('./objix')
const _ = require('lodash')
//const assert = require('assert')
//const ph = require('node:perf_hooks')
const hdr = require('hdr-histogram-js')
const iters = process.argv[2] || 1000 // Number of iterations per heat
const heats = process.argv[3] || 100 // Number of randomised heats
const nSimp = process.argv[4] || 10 // Number of simple object entries
const nDeep = process.argv[5] || 1 // Number of complex object entries
const round = (v, p = 2) => Math.round(v * (10 ** p)) / (10 ** p)
/*
Calculate run time for *heats* batches of *iters* executions of each function, with a randomised execution order for each batch.
Each batch run also includes a 100 iteration warmup verifying the results of the function against objix
*/
function compare(funcs) {
let hist = funcs._map(v => null), start
for (let r = 0; r < heats; r++) for (let [key,fun] of _.shuffle(funcs._entries())) if (fun) {
for (let i = 0; i < 100; i++) assert.deepEqual(funcs.objix(), fun(), fun)
if (!hist[key]) hist[key] = hdr.build()
start = performance.now()
for (let i = 0; i < iters; i++) fun()
hist[key].recordValue(Math.round(iters/(performance.now() - start)))
}
/*
return hist.flatMap((k,v) => [
[k + ' 75%', v.percentiles.get(75)],
[k + ' avg', v.mean],
[k + ' Err', round(100*v.stddev/v.mean)],
[k + ' Inc', round((100*v.mean/hist.lodash.mean)-100)],
])
*/
let res = hist._map(v => v?.mean)
res['% Inc'] = round(100*(hist.objix.mean - hist.lodash.mean)/hist.lodash.mean)
//res['% Err'] = round(100*(hist.objix.stddev + hist.lodash.stddev)/(hist.objix.mean + hist.lodash.mean))
return res
}
function report(title, ob) {
console.log(title)
console.table({
Map: {
objix : () => ob._map(v => v+1),
lodash: () => _.mapValues(ob, v => v+1),
vanilla: () => Object.fromEntries(Object.entries(ob).map(([k,v]) => [k, v+1])),
},
Pick: {
objix: () => ob._pick(v => v == 1),
lodash: () => _.pickBy(ob, v => v == 1),
vanilla: () => Object.fromEntries(Object.entries(ob).flatMap(([k,v]) => v == 1 ? [[k,v]] : [])),
},
/*
'Pick (list)': {
objix: () => ob.pick(['s1','s2','s3']),
lodash: () => _.pick(ob,['s1','s2','s3']),
vanilla: () => Object.fromEntries(Object.entries(ob).flatMap(([k,v]) => ['s1','s2','s3'].includes(k) ? [[k,v]] : [])),
},
*/
Find: {
objix: () => ob._find(v => v == 1),
lodash: () => _.findKey(ob, v => v == 1),
vanilla: () => { for (let [k,v] of Object.entries(ob)) if (v == 1) return k },
},
FlatMap: {
objix: () => ob._flatMap((k,v) => [[k,v],[k+1, v+1]]),
lodash: () => Object.fromEntries(_.flatMap(ob, (v,k) => [[k,v],[k+1, v+1]])),
/*
_lodash: () => {
let r = {}
for (let [k,v] of _.flatMap(ob, (v,k) => [[k,v],[k+1, v+1]])) r[k] = v
return r
}
*/
//vanilla: () => { for (let [k,v] of Object.entries(ob)) if (v == 1) return k },
},
Has: {
objix: () => ob._has(3),
lodash: () => _.includes(ob, 3),
vanilla: () => Object.values(ob).includes(3)
},
KeyBy: {
objix: () => [{a:1},{a:2},{a:3}]._keyBy('a'),
lodash: () => _.keyBy([{a:1},{a:2},{a:3}], 'a'),
},
Equals: {
objix: () => ob._eq(ob._clone(), -1),
lodash: () => _.isEqual(ob, ob._clone()),
vanilla: () => { try { return assert.deepEqual(ob,ob._clone()) || true } catch { return false }},
},
Clone: {
objix: () => ob._clone(),
lodash: () => _.clone(ob),
vanilla: () => Object.assign({}, ob), //No Construcotrs!
},
Deep: {
objix: () => ob._clone(-1),
vanilla: typeof structuredClone !== 'undefined' ? () => structuredClone(ob) : null,
lodash: () => _.cloneDeep(ob),
},
Extend: {
objix: () => ob._extend({a: 1, b: 2, c: 3}),
lodash: () => _.defaults(ob, {a: 1, b:2, c: 2}),
vanilla: () => Object.assign({}, {a: 1, b: 2, c: 3}, ob)
},
Some: {
objix: () => ob._some(v => v == 'x'),
vanilla: () => Object.values(ob).some(v => v == 'x'),
lodash: () => _.some(_.values(ob), v => v == 'x'),
},
Every: {
objix: () => ob._every(v => v),
lodash: () => _.every(_.values(ob), v => v),
vanilla: () => Object.values(ob).every(v => v),
}
}._map(compare))
}
const d1 = new Date()
const d2 = new Date()
const testOb = { }
//const deepOb = { a: { b: { c: [1, 2, 3], d: d1, e:[] }}}
const deepOb = { a: { b: [ 1,2,3,d1, { c: 0, d: d2 }], e:1, f:2, g: 3, h: 4, i: 5, j: []}}
for (let i=0; i < nSimp; i++) testOb['s'+i] = i
for (let i=0; i < nDeep; i++) testOb['d'+i] = deepOb
report(`Ops/sec (iters: ${iters}, heats: ${heats}, simple: ${nSimp}, complex: ${nDeep})`, testOb)