From 7f02ba25ef5f6c726f75f801f05cbe6e571b43d8 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 11 Dec 2024 14:03:40 +0100 Subject: [PATCH] chore: add `eigen` to the matrix_operations.js benchmark. Switch to `tinybench` to make that work --- package-lock.json | 40 ++++++++ package.json | 2 + test/benchmark/matrix_operations.js | 145 ++++++++++++++-------------- 3 files changed, 117 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a769daa2c..232941545d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "core-js": "3.39.0", "del": "8.0.0", "dtslint": "4.2.1", + "eigen": "0.2.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-config-standard": "17.1.0", @@ -77,6 +78,7 @@ "process": "0.11.10", "sinon": "19.0.2", "sylvester": "0.0.21", + "tinybench": "3.0.7", "ts-node": "10.9.2", "typescript": "5.5.4", "webpack": "5.96.1", @@ -2431,6 +2433,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hashmap": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hashmap/-/hashmap-2.3.4.tgz", + "integrity": "sha512-IoFSb7S7cwCM23HcAhUS57DBWPU0dPSF6Wz4M4y0S+B1xgGmM08WOzd1wBldesmZ28jhzRNmXda/FO5uY3tLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -5384,6 +5393,17 @@ "dev": true, "license": "MIT" }, + "node_modules/eigen": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/eigen/-/eigen-0.2.2.tgz", + "integrity": "sha512-M+g7PKbPTJmQFOl60PYj0/jfU43qyuFrrPHZJCQYxfprV6jwRVaVHFJLhGH+eG8y9W/I/YjwzJTmIWLdLv2o2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hashmap": "^2.3.1", + "hashmap": "^2.4.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.63", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.63.tgz", @@ -7663,6 +7683,16 @@ "license": "ISC", "optional": true }, + "node_modules/hashmap": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/hashmap/-/hashmap-2.4.0.tgz", + "integrity": "sha512-Ngj48lhnxJdnBAEVbubKBJuN1elfVLZJs94ZixRi98X3GCU4v6pgj9qRkHt6H8WaVJ69Wv0r1GhtS7hvF9zCgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -12751,6 +12781,16 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-3.0.7.tgz", + "integrity": "sha512-soxV7Dp8eDKvPDv3c4qPJbUjLm1cZxFlsTaIH+FqalsazJzFrLG59dpiIN8OfgVcl11Hfj2b7apD73inCB67Mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", diff --git a/package.json b/package.json index a03355a99e..ee336f1902 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "core-js": "3.39.0", "del": "8.0.0", "dtslint": "4.2.1", + "eigen": "0.2.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-config-standard": "17.1.0", @@ -90,6 +91,7 @@ "process": "0.11.10", "sinon": "19.0.2", "sylvester": "0.0.21", + "tinybench": "3.0.7", "ts-node": "10.9.2", "typescript": "5.5.4", "webpack": "5.96.1", diff --git a/test/benchmark/matrix_operations.js b/test/benchmark/matrix_operations.js index d415ff2cef..79e40fc713 100644 --- a/test/benchmark/matrix_operations.js +++ b/test/benchmark/matrix_operations.js @@ -9,24 +9,20 @@ * has room for improvements, it's not a fully fletched benchmark suite. */ -import Benchmark from 'benchmark' +import { Bench } from 'tinybench' import det from 'ndarray-determinant' import gemm from 'ndarray-gemm' import ops from 'ndarray-ops' import pack from 'ndarray-pack' import numeric from 'numericjs' -import padRight from 'pad-right' import sylvester from 'sylvester' +import eig from 'eigen' import zeros from 'zeros' import { all, create } from '../../lib/esm/index.js' -const suite = new Benchmark.Suite() +const bench = new Bench({ time: 100 }) const math = create(all) -function pad (text) { - return padRight(text, 40, ' ') -} - // fiedler matrix 25 x 25 const fiedler = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], @@ -56,66 +52,75 @@ const fiedler = [ [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] ]; -// mathjs -(function () { - const A = math.matrix(fiedler, 'dense', 'number') - - suite.add(pad('matrix operations mathjs (number) A+A'), function () { return math.add(A, A) }) - suite.add(pad('matrix operations mathjs (number) A*A'), function () { return math.multiply(A, A) }) - suite.add(pad('matrix operations mathjs (number) A\''), function () { return math.transpose(A) }) - suite.add(pad('matrix operations mathjs (number) det(A)'), function () { return math.det(A) }) -})(); - -// mathjs -(function () { - const A = math.matrix(fiedler) - - suite.add(pad('matrix operations mathjs (generic) A+A'), function () { return math.add(A, A) }) - suite.add(pad('matrix operations mathjs (generic) A*A'), function () { return math.multiply(A, A) }) - suite.add(pad('matrix operations mathjs (generic) A\''), function () { return math.transpose(A) }) - suite.add(pad('matrix operations mathjs (generic) det(A)'), function () { return math.det(A) }) -})(); - -// sylvester -(function () { - const A = sylvester.Matrix.create(fiedler) - - suite.add(pad('matrix operations sylvester A+A'), function () { return A.add(A) }) - suite.add(pad('matrix operations sylvester A*A'), function () { return A.multiply(A) }) - suite.add(pad('matrix operations sylvester A\''), function () { return A.transpose() }) - suite.add(pad('matrix operations sylvester det(A)'), function () { return A.det() }) -})(); - -// numericjs -(function () { - const A = fiedler - - suite.add(pad('matrix operations numericjs A+A'), function () { return numeric.add(A, A) }) - suite.add(pad('matrix operations numericjs A*A'), function () { return numeric.dot(A, A) }) - suite.add(pad('matrix operations numericjs A\''), function () { return numeric.transpose(A) }) - suite.add(pad('matrix operations numericjs det(A)'), function () { return numeric.det(A) }) -})(); - -// ndarray -(function () { - const A = pack(fiedler) - const B = zeros([25, 25]) - - suite.add(pad('matrix operations ndarray A+A'), function () { return ops.add(B, A, A) }) - suite.add(pad('matrix operations ndarray A*A'), function () { return gemm(B, A, A) }) - suite.add(pad('matrix operations ndarray A\''), function () { ops.assign(B, A); return B.transpose(1, 0) }) - suite.add(pad('matrix operations ndarray det(A)'), function () { return det(A) }) -})() - -const durations = [] - -suite - .on('cycle', function (event) { - const benchmark = event.target - console.log(String(event.target)) - durations.push(benchmark.name + ' avg duration per operation: ' + Math.round(benchmark.stats.mean * 1e6) + ' microseconds') - }) - .run() - -console.log() -console.log(durations.join('\n')) +(async function () { + // mathjs + (function () { + const A = math.matrix(fiedler, 'dense', 'number') + + bench.add('matrix operations mathjs (number) A+A', function () { return math.add(A, A) }) + bench.add('matrix operations mathjs (number) A*A', function () { return math.multiply(A, A) }) + bench.add('matrix operations mathjs (number) A\'', function () { return math.transpose(A) }) + bench.add('matrix operations mathjs (number) det(A)', function () { return math.det(A) }) + })(); + + // mathjs + (function () { + const A = math.matrix(fiedler) + + bench.add('matrix operations mathjs (generic) A+A', function () { return math.add(A, A) }) + bench.add('matrix operations mathjs (generic) A*A', function () { return math.multiply(A, A) }) + bench.add('matrix operations mathjs (generic) A\'', function () { return math.transpose(A) }) + bench.add('matrix operations mathjs (generic) det(A)', function () { return math.det(A) }) + })(); + + // sylvester + (function () { + const A = sylvester.Matrix.create(fiedler) + + bench.add('matrix operations sylvester A+A', function () { return A.add(A) }) + bench.add('matrix operations sylvester A*A', function () { return A.multiply(A) }) + bench.add('matrix operations sylvester A\'', function () { return A.transpose() }) + bench.add('matrix operations sylvester det(A)', function () { return A.det() }) + })(); + + // numericjs + (function () { + const A = fiedler + + bench.add('matrix operations numericjs A+A', function () { return numeric.add(A, A) }) + bench.add('matrix operations numericjs A*A', function () { return numeric.dot(A, A) }) + bench.add('matrix operations numericjs A\'', function () { return numeric.transpose(A) }) + bench.add('matrix operations numericjs det(A)', function () { return numeric.det(A) }) + })(); + + // ndarray + (function () { + const A = pack(fiedler) + const B = zeros([25, 25]) + + bench.add('matrix operations ndarray A+A', function () { return ops.add(B, A, A) }) + bench.add('matrix operations ndarray A*A', function () { return gemm(B, A, A) }) + bench.add('matrix operations ndarray A\'', function () { ops.assign(B, A); return B.transpose(1, 0) }) + bench.add('matrix operations ndarray det(A)', function () { return det(A) }) + })() + + // eigen-js + await eig.ready + await (function () { + const A = new eig.Matrix(fiedler) + + bench.add('matrix operations eigen-js A+A', function () { return A.matAdd(A) }) + bench.add('matrix operations eigen-js A*A', function () { return A.matMul(A) }) + bench.add('matrix operations eigen-js A\'', function () { return A.transpose() }) + bench.add('matrix operations eigen-js det(A)', function () { return A.det() }) + })() + + await bench.run() + + console.table(bench.tasks.map(({ name, result }) => ({ + Name: name, + 'Ops/sec': Math.round(result?.hz), + 'Average Time (\u00b5s)': result?.mean * 1000, + 'Variance (\u00b5s)': result?.variance * 1000 + }))) +})().catch(console.error)