diff --git a/comparisons/comparisons.bench.ts b/comparisons/comparisons.bench.ts index c8522b2b..4ff7c4fa 100644 --- a/comparisons/comparisons.bench.ts +++ b/comparisons/comparisons.bench.ts @@ -33,6 +33,8 @@ const objects: any = radashi.list(0, 5, i => { return object }) +const array100 = new Array(100) + const benchmarks: Partial< Record> > = { @@ -77,43 +79,40 @@ const benchmarks: Partial< _.group(list, x => x.group) } }, - pick: { - 'with empty keys': _ => { - const input = { a: 2 } - - if (isLodash(_)) { - _.pickBy(input, []) - } else { - _.pick(input, []) - } - }, - 'with key not in object': _ => { - const input = { a: 2, b: 3 } - - if (isLodash(_)) { - _.pickBy(input, ['c']) - } else { - _.pick(input, ['c'] as unknown as ['b']) - } - }, - 'with one key not in object': _ => { - const input = { a: 2, b: 3 } - - if (isLodash(_)) { - _.pickBy(input, ['a', 'c']) - } else { - _.pick(input, ['a', 'c'] as unknown as ['a']) - } - }, - }, + // pick: { + // broken + // 'with empty keys': _ => { + // const input = { a: 2 } + // if (isLodash(_)) { + // _.pickBy(input, []) + // } else { + // _.pick(input, []) + // } + // }, + // 'with key not in object': _ => { + // const input = { a: 2, b: 3 } + // if (isLodash(_)) { + // _.pickBy(input, ['c']) + // } else { + // _.pick(input, ['c'] as unknown as ['b']) + // } + // }, + // 'with one key not in object': _ => { + // const input = { a: 2, b: 3 } + // if (isLodash(_)) { + // _.pickBy(input, ['a', 'c']) + // } else { + // _.pick(input, ['a', 'c'] as unknown as ['a']) + // } + // }, + // }, castArray: { + // ok 'with an array': _ => { - const input = new Array(100) - if (isLodash(_)) { - _.castArray(input) + _.castArray(array100) } else { - _.castArray(input) + _.castArray(array100) } }, 'with number': _ => { @@ -134,6 +133,7 @@ const benchmarks: Partial< }, }, max: { + // ok 'with numbers': _ => { const list = [5, 5, 10, 2] _.max(list) @@ -150,6 +150,7 @@ const benchmarks: Partial< }, }, zip: { + // ok 'with non-empty arrays': _ => { if (isLodash(_)) { _.zip(['a', 'b'], [1, 2], [true, false]) @@ -159,6 +160,7 @@ const benchmarks: Partial< }, }, set: { + // ok 'with simple path': _ => { if (isLodash(_)) { _.set({}, 'foo', 0) @@ -189,6 +191,7 @@ const benchmarks: Partial< }, }, clone: { + // ok 'with object': _ => { const obj = { x: 22, @@ -215,22 +218,6 @@ const benchmarks: Partial< 'with Object.create(null)': _ => { _.isPlainObject(Object.create(null)) }, - 'with non-plain object (Date)': _ => { - _.isPlainObject(new Date()) - }, - 'with namespace object': _ => { - _.isPlainObject(Math) - }, - 'with non-plain object (arguments)': _ => { - function returnArguments() { - // biome-ignore lint/style/noArguments: - return arguments - } - _.isPlainObject(returnArguments()) - }, - 'with null': _ => { - _.isPlainObject(null) - }, }, get: { 'with simple path': _ => { @@ -338,228 +325,228 @@ const benchmarks: Partial< _.isEmpty('abc') }, }, - snake: { - 'with valid input': _ => { - const input = 'hello world' - if (isLodash(_)) { - _.snakeCase(input) - } else { - _.snake(input) - } - }, - }, - title: _ => { - if (isLodash(_)) { - _.startCase('hello world') - } else { - _.title('hello world') - } - }, - dash: _ => { - if (isLodash(_)) { - _.kebabCase('hello world') - } else { - _.dash('hello world') - } - }, - camel: _ => { - if (isLodash(_)) { - _.camelCase('hello world') - } else { - _.camel('hello world') - } - }, - cloneDeep: _ => { - if (isLodash(_)) { - _.cloneDeep(objects) - } else { - _.cloneDeep(objects) - } - }, - flat: _ => { - const lists = [['a', 'b'], ['c', 'd'], ['e']] - if (isLodash(_)) { - _.flatten(lists) - } else { - _.flat(lists) - } - }, - diff: { - 'with entirely different arrays': _ => { - const list1 = ['a', 'b', 'c'] - const list2 = ['c', 'd', 'e'] - if (isLodash(_)) { - _.difference(list1, list2) - } else { - _.diff(list1, list2) - } - }, - 'with identity function': _ => { - const identity = ({ letter }: { letter: string }) => letter - const letter = (l: string) => ({ letter: l }) - const list1 = [letter('a'), letter('b'), letter('c')] - const list2 = [letter('c'), letter('d'), letter('e')] - if (isLodash(_)) { - _.differenceWith(list1, list2, (a, b) => identity(a) === identity(b)) - } else { - _.diff(list1, list2, identity) - } - }, - }, - last: _ => { - const list = [ - { game: 'a', score: 100 }, - { game: 'b', score: 200 }, - ] - if (isLodash(_)) { - _.last(list) - } else { - _.last(list) - } - }, - assign: _ => { - const initial = { - name: 'jay', - cards: ['ac'], - location: { - street: '23 main', - state: { - abbreviation: 'FL', - name: 'Florida', - }, - }, - } - const override = { - name: 'charles', - cards: ['4c'], - location: { - street: '8114 capo', - state: { - abbreviation: 'TX', - name: 2, - a: 2, - }, - }, - } - if (isLodash(_)) { - const a = _.assign(initial, override) - } else { - const a = _.assign(initial, override) - } - }, - isArray: _ => { - _.isArray([]) - }, - isBoolean: { - 'with boolean value': _ => { - _.isBoolean(true) - }, - 'with non-boolean value': _ => { - _.isBoolean(null) - }, - }, - isDate: { - 'with valid input': _ => { - _.isDate(new Date()) - }, - 'with invalid input': _ => { - _.isDate(new Date('invalid value')) - }, - 'with non-Date value': _ => { - _.isDate(22) - }, - }, - cluster: { - 'with default cluster size': _ => { - const list = [1, 1, 1, 1, 1, 1, 1, 1] - if (isLodash(_)) { - _.chunk(list, 2) - } else { - _.cluster(list, 2) - } - }, - 'specified cluster size of 3': _ => { - const list = [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2] + // snake: { + // 'with valid input': _ => { + // const input = 'hello world' + // if (isLodash(_)) { + // _.snakeCase(input) + // } else { + // _.snake(input) + // } + // }, + // }, + // title: _ => { + // if (isLodash(_)) { + // _.startCase('hello world') + // } else { + // _.title('hello world') + // } + // }, + // dash: _ => { + // if (isLodash(_)) { + // _.kebabCase('hello world') + // } else { + // _.dash('hello world') + // } + // }, + // camel: _ => { + // if (isLodash(_)) { + // _.camelCase('hello world') + // } else { + // _.camel('hello world') + // } + // }, + // cloneDeep: _ => { + // if (isLodash(_)) { + // _.cloneDeep(objects) + // } else { + // _.cloneDeep(objects) + // } + // }, + // flat: _ => { + // const lists = [['a', 'b'], ['c', 'd'], ['e']] + // if (isLodash(_)) { + // _.flatten(lists) + // } else { + // _.flat(lists) + // } + // }, + // diff: { + // 'with entirely different arrays': _ => { + // const list1 = ['a', 'b', 'c'] + // const list2 = ['c', 'd', 'e'] + // if (isLodash(_)) { + // _.difference(list1, list2) + // } else { + // _.diff(list1, list2) + // } + // }, + // 'with identity function': _ => { + // const identity = ({ letter }: { letter: string }) => letter + // const letter = (l: string) => ({ letter: l }) + // const list1 = [letter('a'), letter('b'), letter('c')] + // const list2 = [letter('c'), letter('d'), letter('e')] + // if (isLodash(_)) { + // _.differenceWith(list1, list2, (a, b) => identity(a) === identity(b)) + // } else { + // _.diff(list1, list2, identity) + // } + // }, + // }, + // last: _ => { + // const list = [ + // { game: 'a', score: 100 }, + // { game: 'b', score: 200 }, + // ] + // if (isLodash(_)) { + // _.last(list) + // } else { + // _.last(list) + // } + // }, + // assign: _ => { + // const initial = { + // name: 'jay', + // cards: ['ac'], + // location: { + // street: '23 main', + // state: { + // abbreviation: 'FL', + // name: 'Florida', + // }, + // }, + // } + // const override = { + // name: 'charles', + // cards: ['4c'], + // location: { + // street: '8114 capo', + // state: { + // abbreviation: 'TX', + // name: 2, + // a: 2, + // }, + // }, + // } + // if (isLodash(_)) { + // const a = _.assign(initial, override) + // } else { + // const a = _.assign(initial, override) + // } + // }, + // isArray: _ => { + // _.isArray([]) + // }, + // isBoolean: { + // 'with boolean value': _ => { + // _.isBoolean(true) + // }, + // 'with non-boolean value': _ => { + // _.isBoolean(null) + // }, + // }, + // isDate: { + // 'with valid input': _ => { + // _.isDate(new Date()) + // }, + // 'with invalid input': _ => { + // _.isDate(new Date('invalid value')) + // }, + // 'with non-Date value': _ => { + // _.isDate(22) + // }, + // }, + // cluster: { + // 'with default cluster size': _ => { + // const list = [1, 1, 1, 1, 1, 1, 1, 1] + // if (isLodash(_)) { + // _.chunk(list, 2) + // } else { + // _.cluster(list, 2) + // } + // }, + // 'specified cluster size of 3': _ => { + // const list = [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2] - if (isLodash(_)) { - _.chunk(list, 3) - } else { - _.cluster(list, 3) - } - }, - }, - isEqual: { - 'with numbers': _ => { - _.isEqual(1, 1) - }, - 'with string': _ => { - _.isEqual('a', 'a') - }, - 'with object': _ => { - _.isEqual({ a: 1 }, { a: 1 }) - }, - 'with array': _ => { - _.isEqual([1, 2], [1, 2]) - }, - 'with complex object': _ => { - class Person { - constructor(readonly name: string) {} - } - const jake = new Person('jake') - const symbolKey = Symbol('symbol') - const complex = { - num: 0, - str: '', - boolean: true, - unf: void 0, - nul: null, - obj: { name: 'object', id: 1, children: [0, 1, 2] }, - arr: [0, 1, 2], - func() { - console.log('function') - }, - loop: null as any, - person: jake, - date: new Date(0), - reg: /\/regexp\/ig/, - [symbolKey]: 'symbol', - } - complex.loop = complex + // if (isLodash(_)) { + // _.chunk(list, 3) + // } else { + // _.cluster(list, 3) + // } + // }, + // }, + // isEqual: { + // 'with numbers': _ => { + // _.isEqual(1, 1) + // }, + // 'with string': _ => { + // _.isEqual('a', 'a') + // }, + // 'with object': _ => { + // _.isEqual({ a: 1 }, { a: 1 }) + // }, + // 'with array': _ => { + // _.isEqual([1, 2], [1, 2]) + // }, + // 'with complex object': _ => { + // class Person { + // constructor(readonly name: string) {} + // } + // const jake = new Person('jake') + // const symbolKey = Symbol('symbol') + // const complex = { + // num: 0, + // str: '', + // boolean: true, + // unf: void 0, + // nul: null, + // obj: { name: 'object', id: 1, children: [0, 1, 2] }, + // arr: [0, 1, 2], + // func() { + // console.log('function') + // }, + // loop: null as any, + // person: jake, + // date: new Date(0), + // reg: /\/regexp\/ig/, + // [symbolKey]: 'symbol', + // } + // complex.loop = complex - _.isEqual(complex, { ...complex }) - }, - }, - isError: { - 'with error': _ => { - _.isError(new Error()) - }, - 'with non-error': _ => { - _.isError(new Date()) - }, - }, - isInt: { - 'with integer': _ => { - if (isLodash(_)) { - _.isInteger(22) - } else { - _.isInt(22) - } - }, - 'with non-integer': _ => { - if (isLodash(_)) { - _.isInteger(22.0567) - } else { - _.isInt(22.0567) - } - }, - 'with non-number': _ => { - if (isLodash(_)) { - _.isInteger('abc') - } else { - _.isInt('22') - } - }, - }, + // _.isEqual(complex, { ...complex }) + // }, + // }, + // isError: { + // 'with error': _ => { + // _.isError(new Error()) + // }, + // 'with non-error': _ => { + // _.isError(new Date()) + // }, + // }, + // isInt: { + // 'with integer': _ => { + // if (isLodash(_)) { + // _.isInteger(22) + // } else { + // _.isInt(22) + // } + // }, + // 'with non-integer': _ => { + // if (isLodash(_)) { + // _.isInteger(22.0567) + // } else { + // _.isInt(22.0567) + // } + // }, + // 'with non-number': _ => { + // if (isLodash(_)) { + // _.isInteger('abc') + // } else { + // _.isInt('22') + // } + // }, + // }, } for (const [funcName, run] of Object.entries(benchmarks)) { diff --git a/scripts/run-bench.sh b/scripts/run-bench.sh index cef57854..ace4d141 100644 --- a/scripts/run-bench.sh +++ b/scripts/run-bench.sh @@ -4,4 +4,4 @@ if [ ! -d "comparisons/node_modules" ]; then pnpm install --prefix comparisons --reporter=silent fi -vitest bench +vitest bench comparisons/* --run diff --git a/vitest.config.ts b/vitest.config.ts index 6e1a0c11..3c2930d1 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs' +import v8 from 'node:v8' import { defineConfig } from 'vitest/config' const resolve = (specifier: string) => @@ -9,6 +11,22 @@ export default defineConfig(env => ({ include: ['tests/**/*.test.ts'], benchmark: { include: ['(benchmarks|comparisons)/**/*.bench.ts'], + reporters: [ + 'default', + { + onInit() { + // debug heap stats + ;(async () => { + while (true) { + await new Promise(r => setTimeout(r, 1000)) + const stats = v8.getHeapStatistics() + stats.malloced_memory + fs.appendFileSync('./heap.csv', `${stats.used_heap_size}\n`) + } + })() + }, + }, + ], }, coverage: { thresholds: { 100: true }, @@ -18,7 +36,15 @@ export default defineConfig(env => ({ typecheck: { include: ['tests/**/*.test-d.ts'], enabled: true, - tsconfig: "tests/tsconfig.json" + tsconfig: 'tests/tsconfig.json', + }, + pool: env.mode === 'benchmark' ? 'threads' : 'forks', + // temporarily testing to improve performance + poolOptions: { + threads: { + isolate: false, + execArgv: ['cpu-prof-dir', 'disabled-threads-profiling'], + }, }, }, resolve: {