Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up the map() and forEach() functions in DenseMatrix.js by over 300% #3251

Merged
merged 11 commits into from
Sep 27, 2024
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ Adam Jones <[email protected]>
Lucas Eng <[email protected]>
Orel Ben Neriah <[email protected]>
Vistinum <[email protected]>
Nyaaboron <[email protected]>
Vas Sudanagunta <[email protected]>
Brooks Smith <[email protected]>
Jmar L. Pineda <[email protected]>

# Generated by tools/update-authors.js
87 changes: 75 additions & 12 deletions src/type/matrix/DenseMatrix.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// deno-lint-ignore-file no-this-alias
import { isArray, isBigNumber, isCollection, isIndex, isMatrix, isNumber, isString, typeOf } from '../../utils/is.js'
import { arraySize, getArrayDataType, processSizesWildcard, reshape, resize, unsqueeze, validate, validateIndex, broadcastTo, get, recurse } from '../../utils/array.js'
import { arraySize, getArrayDataType, processSizesWildcard, reshape, resize, unsqueeze, validate, validateIndex, broadcastTo, get } from '../../utils/array.js'
import { format } from '../../utils/string.js'
import { isInteger } from '../../utils/number.js'
import { clone, deepStrictEqual } from '../../utils/object.js'
Expand Down Expand Up @@ -524,6 +525,69 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
return this._size.slice(0) // return a clone of _size
}

/**
* Applies a callback function to a reference to each element of the matrix
* @memberof DenseMatrix
* @param {Function} callback The callback function is invoked with three
* parameters: an array, an integer index to that
* array, and the Matrix being traversed.
*/
DenseMatrix.prototype._forEach = function (callback) {
// matrix instance
const me = this
const s = me.size()

// if there is only one dimension, just loop through it
if (s.length === 1) {
for (let i = 0; i < s[0]; i++) {
callback(me._data, i, [i])
}
return
}

// keep track of the current index permutation
const index = Array(s.length).fill(0)

// store a reference of each dimension of the matrix for faster access
const data = Array(s.length - 1)
const last = data.length - 1

data[0] = me._data[0]
for (let i = 0; i < last; i++) {
data[i + 1] = data[i][0]
}

index[last] = -1
while (true) {
let i
for (i = last; i >= 0; i--) {
// march index to the next permutation
index[i]++
if (index[i] === s[i]) {
index[i] = 0
continue
}

// update references to matrix dimensions
data[i] = i === 0 ? me._data[index[i]] : data[i - 1][index[i]]
for (let j = i; j < last; j++) {
data[j + 1] = data[j][0]
}

// loop through the last dimension and map each value
for (let j = 0; j < s[data.length]; j++) {
index[data.length] = j
callback(data[last], j, index.slice(0))
}
break
}

if (i === -1) {
break
}
}
}

/**
* Create a new matrix with the results of the callback function executed on
* each entry of the matrix.
Expand All @@ -535,17 +599,15 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
* @return {DenseMatrix} matrix
*/
DenseMatrix.prototype.map = function (callback) {
// matrix instance
const me = this
const result = new DenseMatrix(me)
const fastCallback = optimizeCallback(callback, me._data, 'map')

// determine the new datatype when the original matrix has datatype defined
// TODO: should be done in matrix constructor instead
const data = recurse(me._data, [], me, fastCallback)
const datatype = me._datatype !== undefined
? getArrayDataType(data, typeOf)
: undefined
return new DenseMatrix(data, datatype)
result._forEach(function (arr, i, index) {
arr[i] = fastCallback(arr[i], index, me)
})

return result
}

/**
Expand All @@ -556,10 +618,11 @@ export const createDenseMatrixClass = /* #__PURE__ */ factory(name, dependencies
* of the element, and the Matrix being traversed.
*/
DenseMatrix.prototype.forEach = function (callback) {
// matrix instance
const me = this
const fastCallback = optimizeCallback(callback, me._data, 'forEach')
recurse(this._data, [], me, fastCallback)
const fastCallback = optimizeCallback(callback, me._data, 'map')
me._forEach(function (arr, i, index) {
fastCallback(arr[i], index, me)
})
}

/**
Expand Down
16 changes: 8 additions & 8 deletions test/unit-tests/type/matrix/DenseMatrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,22 +734,22 @@ describe('DenseMatrix', function () {
const m = new DenseMatrix([[1, 2, 3], [4, 5, 6]])
const m2 = m.map(
function (value, index, obj) {
return JSON.stringify([value, index, obj === m])
return [value, index, obj === m]
}
)

assert.deepStrictEqual(
m2.toArray(),
[
[
'[1,[0,0],true]',
'[2,[0,1],true]',
'[3,[0,2],true]'
[1, [0, 0], true],
[2, [0, 1], true],
[3, [0, 2], true]
],
[
'[4,[1,0],true]',
'[5,[1,1],true]',
'[6,[1,2],true]'
[4, [1, 0], true],
[5, [1, 1], true],
[6, [1, 2], true]
]
])
})
Expand Down Expand Up @@ -792,7 +792,7 @@ describe('DenseMatrix', function () {
const output = []
m.forEach(
function (value, index, obj) {
output.push(math.clone([value, index, obj === m]))
output.push([value, index, obj === m])
}
)
assert.deepStrictEqual(output, [
Expand Down