From b938e04bf086ce3cd97b4cb1d23a8315cf5f22fb Mon Sep 17 00:00:00 2001 From: doleron Date: Wed, 1 Jul 2020 01:22:18 -0300 Subject: [PATCH] improvement in multiply algo + docs --- README.md | 34 +++++++++++++++++----------- benchmarks/benchmark.stuff.js | 10 ++++----- src/api/lowlevelapi.js | 36 +++++++++++++++++++++--------- test/arithmetics/multiplication.js | 8 +++---- test/test.utils.js | 6 +++++ 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 10a6cd2..3d7efeb 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,21 @@ A faster Matrix for the Java Script World. -There are some awesome matrix libraries for Java Script in the wild. +There are awesome matrix libraries for Java Script in the wild. But if you are looking for something faster for numeric computing, consider using this library. ## Benchmarks Some Matrix multiplication benchmarks: -| | 2x2 | 3x3 | 4x4 | 16x16 | 32x32 | 256x256 | 512x512 | -|----------------|:---------:|:---------:|:-------:|:------:|:-----:|:-------:|:-------:| -| Matrix-reef JS | 1,787,425 | 1,557,114 | 869,244 | 45,497 | 6,312 | 16.06 | 1.92 | -| ml-Matrix | 188,606 | 156,872 | 127,508 | 15,417 | 5,319 | 15.76 | 1.89 | -| Math.js | 129,336 | 101,767 | 63,528 | 3,574 | 553 | 0.92 | 0.08 | +| | 2x2 | 3x3 | 4x4 | 16x16 | 32x32 | 256x256 | 512x512 | 1024x1024 | +|----------------|:----------:|:---------:|:---------:|:-------:|:------:|:-------:|:-------:|:---------:| +| Matrix-reef JS | 12,525,475 | 7,767,275 | 4,264,070 | 192,831 | 31,382 | 95.34 | 10.25 | 1.73 | +| Alternative M | 692,706 | 640,598 | 418,942 | 73,875 | 18,161 | 45.08 | 5.40 | 0.69 | +| Alternative S | 2,704,288 | 1,260,948 | 500,337 | 13,602 | 1,847 | 3.81 | 0.33 | 0.04 | +| Alternative J | 452,393 | 388,227 | 230,914 | 11,256 | 1,496 | 2.84 | 0.27 | 0.01 | -\# of operations per second. See [How to run the benchmarks](#how-to-run-the-benchmarks) for details. +\# multiplications per second (achieved on V8/node10.19 @ ubuntu 20.04 2 cpus and 8GB RAM on virtualbox). See [Running the benchmarks](#Running-the-benchmarks) for details. ## Disclaimer Matrix-reef.js is still in its early stages. Some must-to-do features are not available, there is no docs, tutorials and no TypeScript. Thus, I'm afraid that Matrix-reef.js is not ready for production yet. @@ -42,9 +43,12 @@ const B = new Matrix([ const C = A.multiply(B); ``` -See [examples](#API-by-examples) below for a complete list of functionalities and demonstration of how to use them. +See [examples](#API-by-examples) below for a full list of functionalities. + ## How to install +Matrix-reef.js is a pure Java Script library with no third-party dependencies. + ### Node.js ```bash $ npm install matrix-reef.js @@ -58,6 +62,8 @@ const { Matrix } = require('matrix-reef.js'); ``` # API by examples +The API is streamline. Check out the examples below: + - **creating**: [constructors](examples/creational.md#constructors), [zeros, ones](examples/creational.md#zeros-and-ones), [identity](examples/creational.md#identity-aka-eye), [diagonal](examples/creational.md#diagonal) matrix from vector, [cloning](examples/creational.md#clone) and slicing. - **accessing**: [get, set](examples/accessing.md#constructors), row, col, diagonal and path. - **arithmetics**: [add, subtract](examples/arithmetics.md#get-and-set), [add scalar](examples/arithmetics.md#adding-scalar-to-matrix), [add row](examples/arithmetics.md#adding-row-or-column-to-matrix) to matrix and [add column](examples/arithmetics.md#adding-row-or-column-to-matrix) to matrix. @@ -71,22 +77,24 @@ const { Matrix } = require('matrix-reef.js'); ## Real world examples -This [example](examples/ann/training.ann.js) shows how to train a neural network using the [Iris](https://archive.ics.uci.edu/ml/datasets/iris) dataset and only `add`, `multiply`, `dotMultiply` and `transpose`: +This [example](examples/ann/training.ann.js) shows how to train a neural network using [Iris](https://archive.ics.uci.edu/ml/datasets/iris) dataset and only `add`, `multiply`, `dotMultiply`, `map` and `transpose`. You can run it by executing the following command: ```bash $ node examples/training.ann.js ``` -Should output: +THe output should be like: ``` Elapsed time: 0.81 secs for 5000 epochs. ``` ## Running the benchmarks -The benchmark code is [here](blob/master/benchmarks/multiplication.js). This program uses Benchmark.js to compare the matrix-multiplication performance of Matrix-reef.js and two other Java Script Matrix implementations: Math.js and ml-Matrix. -Execute the following command to run a benchmark yourself: +There are several ways to evaluate a library. Here we are focused on throughput (that is, operations/sec). The code for this benchmark is [here](blob/master/benchmarks/multiplication.js). It uses Benchmark.js to compare the performance of Matrix-reef.js and three other Java Script matrix libraries. + +To run the benchmark is easy. Just execute the following command to run a benchmark yourself: ```bash node benchmarks/multiplication.js 10 4 10 5 ``` -The command above executes chunks of 10 `4x10` by `10x5` matrix multiplications. Ps.: Use `--max-old-space-size=4096` when running huge matrices. +The example above executes chunks of 10 `4x10` by `10x5` matrix multiplications. + ## New features, bugs and collaboration Do not hesitate to open a new issue whenever you find something wrong or does not working as expected. Suggestions of new features are pretty welcome as well as collaborations by PR test cases, benchmarks or even new features. \ No newline at end of file diff --git a/benchmarks/benchmark.stuff.js b/benchmarks/benchmark.stuff.js index 6cfcc7b..e428b14 100644 --- a/benchmarks/benchmark.stuff.js +++ b/benchmarks/benchmark.stuff.js @@ -49,7 +49,7 @@ module.exports = { const size = data.length; suite - +/* .add('sylvester', function () { for (let i = 0; i < size; ++i) { @@ -73,7 +73,7 @@ module.exports = { A.mmul(B); } - }) + })*/ .add('Matrix-Reef JS', function () { for (let i = 0; i < size; ++i) { @@ -86,7 +86,7 @@ module.exports = { } }) - .add('MathJS', function () { + /*.add('MathJS', function () { for (let i = 0; i < size; ++i) { const matrices = data[i]; @@ -97,14 +97,14 @@ module.exports = { MathJS.multiply(A, B); } - }) + })*/ .on('cycle', function (event) { console.log(String(event.target)); }) .on('complete', function () { console.log('Fastest is ' + this.filter('fastest').map('name')); }) - .run({ 'async': false, initCount : 3 }); + .run({ 'async': false, initCount : 20, minSamples: 10000, minTime : 100 }); } } \ No newline at end of file diff --git a/src/api/lowlevelapi.js b/src/api/lowlevelapi.js index 4053fbc..a533a2b 100644 --- a/src/api/lowlevelapi.js +++ b/src/api/lowlevelapi.js @@ -96,9 +96,9 @@ module.exports = { map2d: function (list, rows, cols, fun, restore) { const size = list.length; let result = restore; - for (let i = 0; i < size; ++i) { + for (let i = 0; i < rows; ++i) { const i_cols = i * cols; - for (let j = 0; j < size; ++j) { + for (let j = 0; j < cols; ++j) { const index = i_cols + j; result[index] = fun(list[index], i, j); } @@ -159,20 +159,36 @@ module.exports = { multiply: function (a, b, aRows, aCols, bRows, bCols, result) { if (aCols != bRows) throw new Error("Incompatible matrices to multiply"); - let bColumn = (bCols > 1) ? new Array(aCols) : b; + const bColumn = (bCols > 1) ? new Array(aCols) : b; + const safeBar = aCols - 4; + let P1, P2, P3, P4, PE; for (let c = 0; c < bCols; ++c) { if (bCols > 1) { - for (let br = 0; br < aCols; ++br) { - bColumn[br] = b[br * bCols + c]; + let brOffset = c; + for (let br = 0; br < aCols; ++br, brOffset += bCols) { + bColumn[br] = b[brOffset]; } } - for (let r = 0; r < aRows; ++r) { + let raOffset = 0; + let rbOffset = c; + for (let r = 0; r < aRows; ++r, raOffset += aCols, rbOffset += bCols) { let sum = 0; - const r_aCols = r * aCols; - for (let br = 0; br < aCols; ++br) { - sum += a[r_aCols + br] * bColumn[br]; + let br = 0; + for (; br < safeBar;) { + const t = raOffset + br; + P1 = a[t] * bColumn[br]; + P2 = a[t + 1] * bColumn[br + 1]; + P3 = a[t + 2] * bColumn[br + 2]; + P3 = a[t + 3] * bColumn[br + 3]; + br += 4; + sum += P1 + P2 + P3 + P4; + } + for (; br < aCols;) { + PE = a[raOffset + br] * bColumn[br]; + sum += PE; + ++br; } - result[r * bCols + c] = sum; + result[rbOffset] = sum; } } return result; diff --git a/test/arithmetics/multiplication.js b/test/arithmetics/multiplication.js index 78ac9d0..4d888e9 100644 --- a/test/arithmetics/multiplication.js +++ b/test/arithmetics/multiplication.js @@ -133,14 +133,14 @@ describe('blind check multiplication', function () { const size = 1000; - this.timeout(size / 2); + this.timeout(Math.max(size / 2, 20000)); it("generating " + size + " multiplication use cases", function () { for (let i = 0; i < size; ++i) { - const a_rows = Math.round(40 * Math.random()) + 1; - const a_cols = Math.round(40 * Math.random()) + 1; - const b_cols = Math.round(40 * Math.random()) + 1; + const a_rows = Math.round(128 * Math.random()) + 1; + const a_cols = Math.round(128 * Math.random()) + 1; + const b_cols = Math.round(128 * Math.random()) + 1; const a = testUtils.randomArray(a_rows, a_cols, testUtils.rand50); const b = testUtils.randomArray(a_cols, b_cols, testUtils.rand50); diff --git a/test/test.utils.js b/test/test.utils.js index 745f19e..de21721 100644 --- a/test/test.utils.js +++ b/test/test.utils.js @@ -21,6 +21,12 @@ module.exports = { if (arrayCols && cols != arrayCols) throw new Error("Matrix cols and array cols does not match"); const arrayRows = array.length; const storage = matrix.storage; + + const storageSize = storage.length; + if (storageSize != (rows * cols)) + throw new Error("The storage length " + storageSize + + " is not consistent to matrix dimensions [" + rows + ", " + cols + "]."); + if (!arrayCols) { for(let i = 0; i < arrayRows; ++i) { const value = array[i];