From a49b53752d9fa8bc4a1daa7288e873a0ff7e6c0b Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 10 Feb 2026 23:13:06 +0100 Subject: [PATCH] feat(bench): add common JS pattern benchmarks Add 9 new benchmark files for frequently used JS patterns: - array-iteration: for loop vs for...of vs forEach vs reduce - string-concatenation: + vs template literal vs concat vs join - type-checking: typeof vs instanceof vs Array.isArray vs toString - array-filter: filter() vs for loop vs reduce - array-find: find() vs findIndex() vs some() vs for loop - set-vs-array-lookup: Set.has() vs includes() vs indexOf() - object-iteration: for...in vs Object.keys/values/entries - number-parsing: parseInt vs Number vs unary + vs bitwise ops - property-check: in vs hasOwnProperty vs Object.hasOwn vs Reflect.has --- array-filter.mjs | 47 +++++++++++++++++++++++++++ array-find.mjs | 53 ++++++++++++++++++++++++++++++ array-iteration.mjs | 58 +++++++++++++++++++++++++++++++++ number-parsing.mjs | 53 ++++++++++++++++++++++++++++++ object-iteration.mjs | 62 +++++++++++++++++++++++++++++++++++ property-check.mjs | 70 ++++++++++++++++++++++++++++++++++++++++ set-vs-array-lookup.mjs | 68 ++++++++++++++++++++++++++++++++++++++ string-concatenation.mjs | 30 +++++++++++++++++ type-checking.mjs | 40 +++++++++++++++++++++++ 9 files changed, 481 insertions(+) create mode 100644 array-filter.mjs create mode 100644 array-find.mjs create mode 100644 array-iteration.mjs create mode 100644 number-parsing.mjs create mode 100644 object-iteration.mjs create mode 100644 property-check.mjs create mode 100644 set-vs-array-lookup.mjs create mode 100644 string-concatenation.mjs create mode 100644 type-checking.mjs diff --git a/array-filter.mjs b/array-filter.mjs new file mode 100644 index 0000000..1d2ac31 --- /dev/null +++ b/array-filter.mjs @@ -0,0 +1,47 @@ +import { Bench } from 'tinybench' + +import { generateRandomNumberArray } from './benchmark-utils.mjs' + +const size = 10000 +const testArray = generateRandomNumberArray(size, 100) +const threshold = 50 + +const bench = new Bench({ + name: `Array filter with ${size} elements`, + time: 100, +}) + +bench + .add('filter()', () => { + return testArray.filter(value => value > threshold) + }) + .add('for loop + push', () => { + const result = [] + for (let i = 0; i < testArray.length; i++) { + if (testArray[i] > threshold) { + result.push(testArray[i]) + } + } + return result + }) + .add('for...of + push', () => { + const result = [] + for (const value of testArray) { + if (value > threshold) { + result.push(value) + } + } + return result + }) + .add('reduce', () => { + return testArray.reduce((result, value) => { + if (value > threshold) { + result.push(value) + } + return result + }, []) + }) + +await bench.run() + +console.table(bench.table()) diff --git a/array-find.mjs b/array-find.mjs new file mode 100644 index 0000000..027442f --- /dev/null +++ b/array-find.mjs @@ -0,0 +1,53 @@ +import { Bench } from 'tinybench' + +import { generateRandomNumberArray } from './benchmark-utils.mjs' + +const size = 10000 +const testArray = generateRandomNumberArray(size, 100) +// Pick a target that exists somewhere in the middle +const target = testArray[Math.floor(size / 2)] + +const bench = new Bench({ + name: `Array find in ${size} elements`, + time: 100, +}) + +bench + .add('find()', () => { + return testArray.find(value => value === target) + }) + .add('findIndex()', () => { + const index = testArray.findIndex(value => value === target) + return index !== -1 ? testArray[index] : undefined + }) + .add('some()', () => { + let result + testArray.some(value => { + if (value === target) { + result = value + return true + } + return false + }) + return result + }) + .add('for loop', () => { + for (let i = 0; i < testArray.length; i++) { + if (testArray[i] === target) { + return testArray[i] + } + } + return undefined + }) + .add('for...of', () => { + for (const value of testArray) { + if (value === target) { + return value + } + } + return undefined + }) + +await bench.run() + +console.table(bench.table()) diff --git a/array-iteration.mjs b/array-iteration.mjs new file mode 100644 index 0000000..d891346 --- /dev/null +++ b/array-iteration.mjs @@ -0,0 +1,58 @@ +import { Bench } from 'tinybench' + +import { generateRandomNumberArray } from './benchmark-utils.mjs' + +const size = 10000 +const testArray = generateRandomNumberArray(size) + +const bench = new Bench({ + name: `Array iteration with ${size} elements`, + time: 100, +}) + +bench + .add('for loop', () => { + let sum = 0 + for (let i = 0; i < testArray.length; i++) { + sum += testArray[i] + } + return sum + }) + .add('for loop (cached length)', () => { + let sum = 0 + const len = testArray.length + for (let i = 0; i < len; i++) { + sum += testArray[i] + } + return sum + }) + .add('for...of', () => { + let sum = 0 + for (const value of testArray) { + sum += value + } + return sum + }) + .add('forEach', () => { + let sum = 0 + testArray.forEach(value => { + sum += value + }) + return sum + }) + .add('reduce', () => { + return testArray.reduce((sum, value) => sum + value, 0) + }) + .add('while loop', () => { + let sum = 0 + let i = 0 + while (i < testArray.length) { + sum += testArray[i] + i++ + } + return sum + }) + +await bench.run() + +console.table(bench.table()) diff --git a/number-parsing.mjs b/number-parsing.mjs new file mode 100644 index 0000000..0bdd83f --- /dev/null +++ b/number-parsing.mjs @@ -0,0 +1,53 @@ +import { Bench } from 'tinybench' + +const intString = '12345' +const floatString = '123.456' + +const benchInt = new Bench({ + name: 'Integer parsing', + time: 100, +}) + +benchInt + .add('parseInt()', () => { + return parseInt(intString, 10) + }) + .add('Number()', () => { + return Number(intString) + }) + .add('+ (unary plus)', () => { + return +intString + }) + .add('~~ (double bitwise NOT)', () => { + return ~~intString + }) + .add('| 0 (bitwise OR)', () => { + return intString | 0 + }) + .add('>> 0 (bitwise shift)', () => { + return intString >> 0 + }) + +await benchInt.run() + +console.table(benchInt.table()) + +const benchFloat = new Bench({ + name: 'Float parsing', + time: 100, +}) + +benchFloat + .add('parseFloat()', () => { + return parseFloat(floatString) + }) + .add('Number()', () => { + return Number(floatString) + }) + .add('+ (unary plus)', () => { + return +floatString + }) + +await benchFloat.run() + +console.table(benchFloat.table()) diff --git a/object-iteration.mjs b/object-iteration.mjs new file mode 100644 index 0000000..bacbbc1 --- /dev/null +++ b/object-iteration.mjs @@ -0,0 +1,62 @@ +import { Bench } from 'tinybench' + +import { generateRandomObject } from './benchmark-utils.mjs' + +const object = generateRandomObject(500) +const keyCount = Object.keys(object).length + +const bench = new Bench({ + name: `Object iteration with ${keyCount} keys`, + time: 100, +}) + +bench + .add('for...in', () => { + let sum = 0 + for (const key in object) { + sum += object[key] + } + return sum + }) + .add('for...in (hasOwnProperty)', () => { + let sum = 0 + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + sum += object[key] + } + } + return sum + }) + .add('Object.keys() + for', () => { + let sum = 0 + const keys = Object.keys(object) + for (let i = 0; i < keys.length; i++) { + sum += object[keys[i]] + } + return sum + }) + .add('Object.keys() + forEach', () => { + let sum = 0 + Object.keys(object).forEach(key => { + sum += object[key] + }) + return sum + }) + .add('Object.values() + for...of', () => { + let sum = 0 + for (const value of Object.values(object)) { + sum += value + } + return sum + }) + .add('Object.entries() + for...of', () => { + let sum = 0 + for (const [, value] of Object.entries(object)) { + sum += value + } + return sum + }) + +await bench.run() + +console.table(bench.table()) diff --git a/property-check.mjs b/property-check.mjs new file mode 100644 index 0000000..ecdf94a --- /dev/null +++ b/property-check.mjs @@ -0,0 +1,70 @@ +import { Bench } from 'tinybench' + +import { generateRandomObject } from './benchmark-utils.mjs' + +const object = generateRandomObject(500) +const keyCount = Object.keys(object).length +// Pick an existing key +const existingKey = Object.keys(object)[Math.floor(keyCount / 2)] +// Pick a missing key +const missingKey = 'nonexistent_property' + +const benchExisting = new Bench({ + name: `Property check (existing key in ${keyCount} keys)`, + time: 100, +}) + +benchExisting + .add('in operator', () => { + return existingKey in object + }) + .add('hasOwnProperty()', () => { + // eslint-disable-next-line no-prototype-builtins + return object.hasOwnProperty(existingKey) + }) + .add('Object.hasOwn()', () => { + return Object.hasOwn(object, existingKey) + }) + .add('Object.prototype.hasOwnProperty.call()', () => { + return Object.prototype.hasOwnProperty.call(object, existingKey) + }) + .add('!== undefined', () => { + return object[existingKey] !== undefined + }) + .add('Reflect.has()', () => { + return Reflect.has(object, existingKey) + }) + +await benchExisting.run() + +console.table(benchExisting.table()) + +const benchMissing = new Bench({ + name: `Property check (missing key in ${keyCount} keys)`, + time: 100, +}) + +benchMissing + .add('in operator', () => { + return missingKey in object + }) + .add('hasOwnProperty()', () => { + // eslint-disable-next-line no-prototype-builtins + return object.hasOwnProperty(missingKey) + }) + .add('Object.hasOwn()', () => { + return Object.hasOwn(object, missingKey) + }) + .add('Object.prototype.hasOwnProperty.call()', () => { + return Object.prototype.hasOwnProperty.call(object, missingKey) + }) + .add('!== undefined', () => { + return object[missingKey] !== undefined + }) + .add('Reflect.has()', () => { + return Reflect.has(object, missingKey) + }) + +await benchMissing.run() + +console.table(benchMissing.table()) diff --git a/set-vs-array-lookup.mjs b/set-vs-array-lookup.mjs new file mode 100644 index 0000000..30111c8 --- /dev/null +++ b/set-vs-array-lookup.mjs @@ -0,0 +1,68 @@ +import { Bench } from 'tinybench' + +import { generateRandomNumberArray } from './benchmark-utils.mjs' + +const size = 10000 +const testArray = generateRandomNumberArray(size, 100000) +const testSet = new Set(testArray) +// Pick a target that exists +const existingTarget = testArray[Math.floor(size / 2)] +// Pick a target that doesn't exist +const missingTarget = -1 + +const bench = new Bench({ + name: `Lookup in ${size} elements (existing value)`, + time: 100, +}) + +bench + .add('Set.has()', () => { + return testSet.has(existingTarget) + }) + .add('Array.includes()', () => { + return testArray.includes(existingTarget) + }) + .add('Array.indexOf() !== -1', () => { + return testArray.indexOf(existingTarget) !== -1 + }) + .add('for loop', () => { + for (let i = 0; i < testArray.length; i++) { + if (testArray[i] === existingTarget) { + return true + } + } + return false + }) + +await bench.run() + +console.table(bench.table()) + +// Also benchmark missing value lookup +const benchMissing = new Bench({ + name: `Lookup in ${size} elements (missing value)`, + time: 100, +}) + +benchMissing + .add('Set.has()', () => { + return testSet.has(missingTarget) + }) + .add('Array.includes()', () => { + return testArray.includes(missingTarget) + }) + .add('Array.indexOf() !== -1', () => { + return testArray.indexOf(missingTarget) !== -1 + }) + .add('for loop', () => { + for (let i = 0; i < testArray.length; i++) { + if (testArray[i] === missingTarget) { + return true + } + } + return false + }) + +await benchMissing.run() + +console.table(benchMissing.table()) diff --git a/string-concatenation.mjs b/string-concatenation.mjs new file mode 100644 index 0000000..bb25d98 --- /dev/null +++ b/string-concatenation.mjs @@ -0,0 +1,30 @@ +import { Bench } from 'tinybench' + +const str1 = 'Hello' +const str2 = 'World' +const str3 = 'JavaScript' +const str4 = 'Benchmark' +const str5 = 'Test' + +const bench = new Bench({ + name: 'String concatenation', + time: 100, +}) + +bench + .add('+ operator', () => { + return str1 + ' ' + str2 + ' ' + str3 + ' ' + str4 + ' ' + str5 + }) + .add('template literal', () => { + return `${str1} ${str2} ${str3} ${str4} ${str5}` + }) + .add('concat()', () => { + return str1.concat(' ', str2, ' ', str3, ' ', str4, ' ', str5) + }) + .add('array join', () => { + return [str1, str2, str3, str4, str5].join(' ') + }) + +await bench.run() + +console.table(bench.table()) diff --git a/type-checking.mjs b/type-checking.mjs new file mode 100644 index 0000000..4f232e6 --- /dev/null +++ b/type-checking.mjs @@ -0,0 +1,40 @@ +import { Bench } from 'tinybench' + +const testValues = { + array: [1, 2, 3], + boolean: true, + null: null, + number: 42, + object: { a: 1 }, + string: 'hello', + undefined, +} + +const bench = new Bench({ + name: 'Type checking', + time: 100, +}) + +// Test with array (common case for type checking) +const testArray = testValues.array + +bench + .add('typeof', () => { + return typeof testArray + }) + .add('instanceof', () => { + return testArray instanceof Array + }) + .add('Array.isArray()', () => { + return Array.isArray(testArray) + }) + .add('Object.prototype.toString.call()', () => { + return Object.prototype.toString.call(testArray) === '[object Array]' + }) + .add('constructor.name', () => { + return testArray.constructor.name === 'Array' + }) + +await bench.run() + +console.table(bench.table()) -- 2.53.0