diff --git a/tests/bench_includes_breakdown.cjs b/tests/bench_includes_breakdown.cjs new file mode 100644 index 0000000..d0aaba9 --- /dev/null +++ b/tests/bench_includes_breakdown.cjs @@ -0,0 +1,147 @@ +function runBench(label, iterations, fn, options) { + const warmup = options && options.warmup ? options.warmup : 5; + const samples = options && options.samples ? options.samples : 10; + + for (let i = 0; i < warmup; i++) fn(); + + let min = Infinity; + let max = -Infinity; + let total = 0; + let last = 0; + + for (let i = 0; i < samples; i++) { + const start = performance.now(); + last = fn(); + const elapsed = performance.now() - start; + if (elapsed < min) min = elapsed; + if (elapsed > max) max = elapsed; + total += elapsed; + } + + const mean = total / samples; + const opsPerMs = iterations / mean; + + console.log(label); + console.log(` iterations: ${iterations}`); + console.log(` result: ${last}`); + console.log(` mean: ${mean.toFixed(3)} ms`); + console.log(` min: ${min.toFixed(3)} ms`); + console.log(` max: ${max.toFixed(3)} ms`); + console.log(` ops/ms: ${opsPerMs.toFixed(1)}`); +} + +const dense = []; +for (let i = 0; i < 1024; i++) dense.push(i); + +const sparse = Array(1024); + +const genericOwn = { + get length() { + return 1024; + }, + get 1023() { + return 1; + } +}; + +const genericProtoBase = { 1023: 1 }; +const genericProto = Object.create(genericProtoBase); +Object.defineProperty(genericProto, "length", { + configurable: true, + enumerable: true, + get() { + return 1024; + } +}); + +const proxy = new Proxy( + { length: 4, 0: NaN, 1: 0, 2: NaN, 3: 1 }, + { + get(target, key) { + return target[key]; + } + } +); + +const typed = new Uint32Array(1024); +typed[1023] = 1; + +runBench("dense hit tail", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if (dense.includes(1023)) hits++; + } + return hits; +}); + +runBench("dense miss", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if (dense.includes(-1)) hits++; + } + return hits; +}); + +runBench("dense hit negative fromIndex", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if (dense.includes(1023, -1)) hits++; + } + return hits; +}); + +runBench("sparse includes(undefined)", 50000, () => { + let hits = 0; + for (let r = 0; r < 50000; r++) { + if (sparse.includes(undefined)) hits++; + } + return hits; +}); + +runBench("sparse miss non-undefined", 50000, () => { + let hits = 0; + for (let r = 0; r < 50000; r++) { + if (sparse.includes(1)) hits++; + } + return hits; +}); + +runBench("generic own getter", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if ([].includes.call(genericOwn, 1, 1023)) hits++; + } + return hits; +}); + +runBench("generic inherited value", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if ([].includes.call(genericProto, 1, 1023)) hits++; + } + return hits; +}); + +runBench("proxy NaN fromIndex", 20000, () => { + let hits = 0; + for (let r = 0; r < 20000; r++) { + if (Array.prototype.includes.call(proxy, NaN, 1)) hits++; + } + return hits; +}); + +runBench("typedarray hit", 50000, () => { + let hits = 0; + for (let r = 0; r < 50000; r++) { + if (typed.includes(1)) hits++; + } + return hits; +}); + +runBench("typedarray miss", 50000, () => { + let hits = 0; + for (let r = 0; r < 50000; r++) { + if (typed.includes(2)) hits++; + } + return hits; +}); diff --git a/tests/test_array_includes_regression_big.cjs b/tests/test_array_includes_regression_big.cjs new file mode 100644 index 0000000..7da1109 --- /dev/null +++ b/tests/test_array_includes_regression_big.cjs @@ -0,0 +1,86 @@ +const assert = require("node:assert"); + +function assertArrayIncludesRegressionSurface() { + assert.strictEqual([1, 2, 3].includes(2), true); + assert.strictEqual([1, 2, 3].includes(4), false); + assert.strictEqual([1, 2, 3].includes(1, 1), false); + assert.strictEqual([NaN].includes(NaN), true); + assert.strictEqual([1, 2, 3].includes(3, -1), true); + assert.strictEqual([1, 2, 3].includes(1, -1), false); + + assert.strictEqual([,].includes(undefined), true); + assert.strictEqual(Array(4).includes(undefined), true); + assert.strictEqual(Array(4).includes(1), false); + + const holey = []; + holey[3] = "tail"; + assert.strictEqual(holey.includes(undefined), true); + assert.strictEqual(holey.includes("tail"), true); + assert.strictEqual(holey.includes("tail", 4), false); + + let steps = 0; + const generic = { + get length() { + steps += 1; + return 6; + }, + get 2() { + steps += 10; + return "match"; + }, + get 4() { + steps += 100; + return "late"; + } + }; + assert.strictEqual([].includes.call(generic, "match", 1), true); + assert.strictEqual(steps, 11); + + const proto = { 3: "from-proto" }; + const inherited = Object.create(proto); + Object.defineProperty(inherited, "length", { + configurable: true, + enumerable: true, + get() { + return 5; + } + }); + assert.strictEqual([].includes.call(inherited, "from-proto"), true); + assert.strictEqual([].includes.call(inherited, undefined), true); + + const proxyLog = []; + const proxy = new Proxy( + { length: 4, 0: NaN, 1: "", 2: NaN, 3: "" }, + { + get(target, key) { + proxyLog.push(String(key)); + return target[key]; + } + } + ); + assert.strictEqual(Array.prototype.includes.call(proxy, NaN, 1), true); + assert.deepStrictEqual(proxyLog, ["length", "1", "2"]); + + [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array + ].forEach((TypedArray) => { + const view = new TypedArray([1, 2, 3]); + assert.strictEqual(view.includes(1), true); + assert.strictEqual(view.includes(4), false); + assert.strictEqual(view.includes(1, 1), false); + }); + + const floatView = new Float64Array([1, NaN, 3]); + assert.strictEqual(floatView.includes(NaN), true); +} + +assertArrayIncludesRegressionSurface(); +console.log("array includes regression surface passes");