Skip to content

Commit

Permalink
Test: Add filter to benchmark, add option to compare with `fast-deep-…
Browse files Browse the repository at this point in the history
…equal`

Follows #1704.

== Exclude ==

Exclude Map/Set tests from the comparison because, unlike QUnit,
fast-deep-equal does not perform a deep equal but a shallow strict
equal when it comes to Map and Set.

```js
const QUnit = require('qunit');
const fde = require('fast-deep-equal/es6');

a = new Set([ { x: 1 }, { y: 2 } ]);
b = new Set([ { x: 1 }, { y: 2 } ]);
QUnit.equiv(a, b); // true
fde(a, b); // false
```

== Local alias ==

Assign local `equiv` in the bench.js file. Without this, even when a
dummy implementation like `QUnit.equiv = (a, b) { return a === b; }`
was still reported as 3X slower than fast-deep-equal holding the
identical function.

```
require('fast-deep-equal/es6').toString().slice(0, 100);
//> 'function equal(a, b) { if (a === b) return true; // …'

require('qunit').equiv+'';
//> 'function equiv(a, b) { if (a === b) return true; // …'
```

For example (with the above fake implementation):

```
fast-deep-equal (primitives) x 648,076 ops/sec ±0.27% (89 runs sampled)
QUnit.innerEquiv (primitives) x 255,011 ops/sec ±0.22% (90 runs sampled)
```

I eventually figured out this is because without the alias, our
benchmark case has to resolve "QUnit" of `QUnit.equiv` through every
lexical scope up to the global sope, and then access the (theoretically
mutable) "equiv" property, and so this every time. For very sensitive
workloads (like the "QUnit.equiv primitives" fixture), which basically
just hit the first `a === b` condition and return early, this is visible
in the benchmark results.
  • Loading branch information
Krinkle committed Oct 3, 2022
1 parent cc9dbd8 commit 6353968
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 12 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"build": "rollup -c && grunt copy:src-css",
"build-coverage": "rollup -c --environment BUILD_TARGET:coverage && grunt copy:src-css",
"build-dev": "node build/watch.js",
"benchmark": "npm install --silent --no-audit --prefix test/benchmark/ && node test/benchmark/index-node.js",
"lint": "eslint --cache .",
"lint-fix": "eslint --cache --fix .",
"test-main": "grunt test",
Expand Down
4 changes: 2 additions & 2 deletions src/equiv.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ const callbacks = {
// Define sets a and b to be equivalent if for each element aVal in a, there
// is some element bVal in b such that aVal and bVal are equivalent. Element
// repetitions are not counted, so these are equivalent:
// a = new Set( [ {}, [], [] ] );
// b = new Set( [ {}, {}, [] ] );
// a = new Set( [ X={}, Y=[], Y ] );
// b = new Set( [ Y, X, X ] );
set (a, b) {
if (a.size !== b.size) {
// This optimization has certain quirks because of the lack of
Expand Down
12 changes: 12 additions & 0 deletions test/benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,15 @@ The default is to benchmark the local development version of QUnit.
* Check the console output.

Powered by [Benchmark.js](https://benchmarkjs.com/).

### Compare against `fast-deep-equal`

```
qunit/test/benchmark$ node index-node.js fast-deep-equal
```

### Run a subset only

```
qunit/test/benchmark$ node index-node.js fast-deep-equal 'small array'
```
32 changes: 22 additions & 10 deletions test/benchmark/bench.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
/* global require, globalThis, QUnitFixtureEquiv, QUnit, console */
/* global require, globalThis, QUnitFixtureEquiv, QUnitBenchInject, QUnit, console */

const Benchmark = typeof require === 'function' ? require('benchmark') : globalThis.Benchmark;
const suite = new Benchmark.Suite();
const inject = typeof QUnitBenchInject === 'function' ? QUnitBenchInject : function () {};
const equiv = QUnit.equiv;

// Check for correctness first, mainly for the return value,
// but also for any unexpected exceptions as Benchmark will tolerate
// uncaught exceptions as being benchmarkable behaviour.
for (const group of QUnitFixtureEquiv) {
if (inject('accept', group) === false) {
continue;
}
group.pairs.forEach((pair, i) => {
const res = QUnit.equiv(pair.a, pair.b);
const res = equiv(pair.a, pair.b);
if (res !== pair.equal) {
throw new Error(`Unexpected return value in "${group.name}" at pairs[${i}]\n Expected: ${pair.equal}\n Actual: ${res}`);
}
});
inject('test', group);
}

suite.add('equiv', function () {
for (const group of QUnitFixtureEquiv) {
for (const pair of group.pairs) {
QUnit.equiv(pair.a, pair.b);
if (inject('accept', { name: '' }) !== false) {
suite.add('QUnit.equiv', function () {
for (const group of QUnitFixtureEquiv) {
for (const pair of group.pairs) {
equiv(pair.a, pair.b);
}
}
}
});
});
}

for (const group of QUnitFixtureEquiv) {
suite.add(`equiv (${group.name})`, function () {
if (inject('accept', group) === false) {
continue;
}
inject('add', group, suite);
suite.add(`QUnit.equiv (${group.name})`, function () {
for (const pair of group.pairs) {
QUnit.equiv(pair.a, pair.b);
equiv(pair.a, pair.b);
}
});
}
Expand Down
29 changes: 29 additions & 0 deletions test/benchmark/index-node.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
/* eslint-env node */

global.QUnit = require('qunit');

if (process.argv[2] === 'fast-deep-equal') {
const fde = require('fast-deep-equal/es6');
const accept = process.argv[3]
? (name) => name.includes(process.argv[3])
: (name) => !/map|set/i.test(name);
global.QUnitBenchInject = function (hook, group, suite) {
if (hook === 'accept') {
return accept(group.name);
}
if (hook === 'test') {
group.pairs.forEach((pair, i) => {
const res = fde(pair.a, pair.b);
if (res !== pair.equal) {
console.log(pair);
throw new Error(`Unexpected fast-deep-equal return in "${group.name}" at pairs[${i}]\n Expected: ${pair.equal}\n Actual: ${res}`);
}
});
}
if (hook === 'add') {
suite.add(`fast-deep-equal (${group.name})`, function () {
for (const pair of group.pairs) {
fde(pair.a, pair.b);
}
});
}
};
}

require('./fixture-equiv.js');
require('./bench.js');
1 change: 1 addition & 0 deletions test/benchmark/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"private": true,
"devDependencies": {
"fast-deep-equal": "3.1.3",
"qunit": "file:../.."
}
}

0 comments on commit 6353968

Please sign in to comment.