From 86b36212f6e48d5f87e4ad5e4fbcf30d50c49a7e Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 9 Feb 2023 22:07:11 +0100 Subject: [PATCH] benchmark: rework assert benchmarks for correctness This reworks most assert benchmarks to provide more reliable test cases that also test more cases than before while keeping the runtime low. Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/46593 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- benchmark/assert/deepequal-buffer.js | 42 +++++++++---- benchmark/assert/deepequal-object.js | 33 +++++----- .../deepequal-prims-and-objs-big-loop.js | 60 ++++++++++++++++--- ...t.js => deepequal-simple-array-and-set.js} | 42 +++++-------- benchmark/assert/deepequal-typedarrays.js | 18 +++--- benchmark/assert/ok.js | 17 ------ benchmark/assert/throws.js | 42 ------------- 7 files changed, 123 insertions(+), 131 deletions(-) rename benchmark/assert/{deepequal-prims-and-objs-big-array-set.js => deepequal-simple-array-and-set.js} (54%) delete mode 100644 benchmark/assert/ok.js delete mode 100644 benchmark/assert/throws.js diff --git a/benchmark/assert/deepequal-buffer.js b/benchmark/assert/deepequal-buffer.js index 69cca91cc6d752..1b6aa3bee1d6ac 100644 --- a/benchmark/assert/deepequal-buffer.js +++ b/benchmark/assert/deepequal-buffer.js @@ -6,27 +6,47 @@ const bench = common.createBenchmark(main, { n: [2e4], len: [1e2, 1e3], strict: [0, 1], - method: ['deepEqual', 'notDeepEqual'], + arrayBuffer: [0, 1], + method: ['deepEqual', 'notDeepEqual', 'unequal_length'], +}, { + combinationFilter: (p) => { + return p.strict === 1 || p.method === 'deepEqual'; + }, }); -function main({ len, n, method, strict }) { - const data = Buffer.allocUnsafe(len + 1); - const actual = Buffer.alloc(len); - const expected = Buffer.alloc(len); - const expectedWrong = Buffer.alloc(len + 1); - data.copy(actual); - data.copy(expected); - data.copy(expectedWrong); +function main({ len, n, method, strict, arrayBuffer }) { + let actual = Buffer.alloc(len); + let expected = Buffer.alloc(len + Number(method === 'unequal_length')); + + + if (method === 'unequal_length') { + method = 'notDeepEqual'; + } + + for (let i = 0; i < len; i++) { + actual.writeInt8(i % 128, i); + expected.writeInt8(i % 128, i); + } + + if (method.includes('not')) { + const position = Math.floor(len / 2); + expected[position] = expected[position] + 1; + } if (strict) { method = method.replace('eep', 'eepStrict'); } + const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + if (arrayBuffer) { + actual = actual.buffer; + expected = expected.buffer; + } bench.start(); for (let i = 0; i < n; ++i) { - fn(actual, value2); + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/deepequal-object.js b/benchmark/assert/deepequal-object.js index 7418e2a745ba40..80bb50f7982529 100644 --- a/benchmark/assert/deepequal-object.js +++ b/benchmark/assert/deepequal-object.js @@ -4,14 +4,20 @@ const common = require('../common.js'); const assert = require('assert'); const bench = common.createBenchmark(main, { - n: [5e3], - size: [1e2, 1e3, 5e4], - strict: [0, 1], + n: [25, 2e2, 2e3], + size: [1e2, 1e3, 1e4], + strict: [1], method: ['deepEqual', 'notDeepEqual'], +}, { + combinationFilter: (p) => { + return p.size === 1e4 && p.n === 25 || + p.size === 1e3 && p.n === 2e2 || + p.size === 1e2 && p.n === 2e3; + }, }); -function createObj(source, add = '') { - return source.map((n) => ({ +function createObj(size, add = '') { + return Array.from({ length: size }, (n) => ({ foo: 'yarp', nope: { bar: `123${add}`, @@ -24,22 +30,17 @@ function createObj(source, add = '') { } function main({ size, n, method, strict }) { - const len = Math.min(Math.ceil(n / size), 20); - - const source = Array.apply(null, Array(size)); - const actual = createObj(source); - const expected = createObj(source); - const expectedWrong = createObj(source, '4'); - if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + const actual = createObj(size); + const expected = method.includes('not') ? createObj(size, '4') : createObj(size); bench.start(); - for (let i = 0; i < len; ++i) { - fn(actual, value2); + for (let i = 0; i < n; ++i) { + fn(actual, expected); } - bench.end(len); + bench.end(n); } diff --git a/benchmark/assert/deepequal-prims-and-objs-big-loop.js b/benchmark/assert/deepequal-prims-and-objs-big-loop.js index 2d01431b1fc563..1ab4ff4dd81f33 100644 --- a/benchmark/assert/deepequal-prims-and-objs-big-loop.js +++ b/benchmark/assert/deepequal-prims-and-objs-big-loop.js @@ -2,35 +2,77 @@ const common = require('../common.js'); const assert = require('assert'); +const circular = {}; +circular.circular = circular; +const circular2 = {}; +circular2.circular = circular2; +const notCircular = {}; +notCircular.circular = {}; + const primValues = { - 'string': 'a', - 'number': 1, - 'object': { 0: 'a' }, + 'string': 'abcdef', + 'number': 1_000, + 'boolean': true, + 'object': { property: 'abcdef' }, + 'object_other_property': { property: 'abcdef' }, + 'array': [1, 2, 3], + 'set_object': new Set([[1]]), + 'set_simple': new Set([1, 2, 3]), + 'circular': circular, + 'empty_object': {}, + 'regexp': /abc/i, + 'date': new Date(), +}; + +const primValues2 = { + 'object': { property: 'abcdef' }, 'array': [1, 2, 3], + 'set_object': new Set([[1]]), + 'set_simple': new Set([1, 3, 2]), + 'circular': circular2, + 'empty_object': {}, + 'regexp': /abc/i, + 'date': new Date(primValues.date), +}; + +const primValuesUnequal = { + 'string': 'abcdez', + 'number': 1_001, + 'boolean': false, + 'object': { property2: 'abcdef' }, + 'array': [1, 3, 2], + 'set_object': new Set([[2]]), + 'set_simple': new Set([1, 4, 2]), + 'circular': notCircular, + 'empty_object': [], + 'regexp': /abc/g, + 'date': new Date(primValues.date.getTime() + 1), }; const bench = common.createBenchmark(main, { primitive: Object.keys(primValues), - n: [2e4], + n: [1e5], strict: [0, 1], method: ['deepEqual', 'notDeepEqual'], +}, { + combinationFilter: (p) => { + return p.strict === 1 || p.method === 'deepEqual'; + }, }); function main({ n, primitive, method, strict }) { const prim = primValues[primitive]; - const actual = prim; - const expected = prim; - const expectedWrong = 'b'; + const actual = primValues2[primitive] ?? prim; + const expected = method.includes('not') ? primValuesUnequal[primitive] : prim; if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; bench.start(); for (let i = 0; i < n; ++i) { - fn([actual], [value2]); + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/deepequal-prims-and-objs-big-array-set.js b/benchmark/assert/deepequal-simple-array-and-set.js similarity index 54% rename from benchmark/assert/deepequal-prims-and-objs-big-array-set.js rename to benchmark/assert/deepequal-simple-array-and-set.js index ad049ded02ce9d..a1f6820696d7b8 100644 --- a/benchmark/assert/deepequal-prims-and-objs-big-array-set.js +++ b/benchmark/assert/deepequal-simple-array-and-set.js @@ -4,18 +4,10 @@ const common = require('../common.js'); const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } = require('assert'); -const primValues = { - 'string': 'a', - 'number': 1, - 'object': { 0: 'a' }, - 'array': [1, 2, 3], -}; - const bench = common.createBenchmark(main, { - primitive: Object.keys(primValues), - n: [25], - len: [2e4], - strict: [0, 1], + n: [5e2], + len: [1e4], + strict: [1], method: [ 'deepEqual_Array', 'notDeepEqual_Array', @@ -32,38 +24,32 @@ function run(fn, n, actual, expected) { bench.end(n); } -function main({ n, len, primitive, method, strict }) { - const prim = primValues[primitive]; +function main({ n, len, method, strict }) { const actual = []; const expected = []; - const expectedWrong = []; - for (let x = 0; x < len; x++) { - actual.push(prim); - expected.push(prim); - expectedWrong.push(prim); + for (let i = 0; i < len; i++) { + actual.push(i); + expected.push(i); + } + if (method.includes('not')) { + expected[len - 1] += 1; } - expectedWrong.pop(); - expectedWrong.push('b'); - - // Note: primitives are only added once to a set - const actualSet = new Set(actual); - const expectedSet = new Set(expected); - const expectedWrongSet = new Set(expectedWrong); switch (method) { case 'deepEqual_Array': run(strict ? deepStrictEqual : deepEqual, n, actual, expected); break; case 'notDeepEqual_Array': - run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expectedWrong); + run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected); break; case 'deepEqual_Set': - run(strict ? deepStrictEqual : deepEqual, n, actualSet, expectedSet); + run(strict ? deepStrictEqual : deepEqual, + n, new Set(actual), new Set(expected)); break; case 'notDeepEqual_Set': run(strict ? notDeepStrictEqual : notDeepEqual, - n, actualSet, expectedWrongSet); + n, new Set(actual), new Set(expected)); break; default: throw new Error(`Unsupported method "${method}"`); diff --git a/benchmark/assert/deepequal-typedarrays.js b/benchmark/assert/deepequal-typedarrays.js index 188cfce695ed61..c06ff8fb2f3099 100644 --- a/benchmark/assert/deepequal-typedarrays.js +++ b/benchmark/assert/deepequal-typedarrays.js @@ -7,8 +7,7 @@ const bench = common.createBenchmark(main, { 'Int8Array', 'Uint8Array', 'Float32Array', - 'Float64Array', - 'Uint8ClampedArray', + 'Uint32Array', ], n: [5e2], strict: [0, 1], @@ -23,21 +22,24 @@ function main({ type, n, len, method, strict }) { const clazz = global[type]; const actual = new clazz(len); const expected = new clazz(len); - const expectedWrong = new clazz(len); - const wrongIndex = Math.floor(len / 2); - expectedWrong[wrongIndex] = 123; if (strict) { method = method.replace('eep', 'eepStrict'); } const fn = assert[method]; - const value2 = method.includes('not') ? expectedWrong : expected; + + if (method.includes('not')) { + expected[Math.floor(len / 2)] = 123; + } bench.start(); for (let i = 0; i < n; ++i) { actual[0] = i; - value2[0] = i; - fn(actual, value2); + expected[0] = i; + const pos = Math.ceil(len / 2) + 1; + actual[pos] = i; + expected[pos] = i; + fn(actual, expected); } bench.end(n); } diff --git a/benchmark/assert/ok.js b/benchmark/assert/ok.js deleted file mode 100644 index 42fd2e89b78d1e..00000000000000 --- a/benchmark/assert/ok.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const common = require('../common.js'); -const assert = require('assert'); - -const bench = common.createBenchmark(main, { n: [1e5] }); - -function main({ n }) { - bench.start(); - for (let i = 0; i < n; ++i) { - if (i % 2 === 0) - assert(true); - else - assert(true, 'foo bar baz'); - } - bench.end(n); -} diff --git a/benchmark/assert/throws.js b/benchmark/assert/throws.js deleted file mode 100644 index 978ad2f1b8bef0..00000000000000 --- a/benchmark/assert/throws.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const common = require('../common.js'); -const { throws, doesNotThrow } = require('assert'); - -const bench = common.createBenchmark(main, { - n: [1e4], - method: [ 'doesNotThrow', 'throws_TypeError', 'throws_RegExp' ], -}); - -function main({ n, method }) { - const throwError = () => { throw new TypeError('foobar'); }; - const doNotThrowError = () => { return 'foobar'; }; - const regExp = /foobar/; - const message = 'failure'; - - switch (method) { - case 'doesNotThrow': - bench.start(); - for (let i = 0; i < n; ++i) { - doesNotThrow(doNotThrowError); - } - bench.end(n); - break; - case 'throws_TypeError': - bench.start(); - for (let i = 0; i < n; ++i) { - throws(throwError, TypeError, message); - } - bench.end(n); - break; - case 'throws_RegExp': - bench.start(); - for (let i = 0; i < n; ++i) { - throws(throwError, regExp, message); - } - bench.end(n); - break; - default: - throw new Error(`Unsupported method ${method}`); - } -}