-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Performance issues in deepMap
#3262
Comments
Is |
Good point. I think we can either pass a flag internally to tell whether or not indices are needed, or we could check something like |
Hi Jos and Athan, The implementation of I will do some tests and report back, maybe it's a simpler fix that could be implemented before #3256 or is something that can be fixed within #3256. It might take a few days. Maybe unrelatedReading the comments it seems that the option to skip zeros in mathjs/test/unit-tests/type/matrix/utils/deepMap.test.js Lines 56 to 67 in 0f87a7b
Currently there is some duplicated code with the various recurse functions and deepMap maybe when the performance issue is found, these different methods of iteration could be merged in a common place. |
Hi Jos and Athan, You were right on the money! By doing both; fixing
As a summary now I made the following branch as a proof of concept. Please review as I think there are cleaner ways to know if the callback is called with 1, 2 or 3 arguments. Jos, now I agree with you that this should be fixed after #3256 due to the refactors of AppendixI did some tests in regular javascript and it seems that for regular functions (not typed) it doesn't affect that much in performance whether the callback is |
After some tests, I think I found another opportunity to improve on performance besides the previously discussed. Seeing how fast is Proof of conceptFor typed functions it would be nice to save the previously found signature implementation and try to use that first.
// Generate a matrix with random values between -1 and 1
const matrix = math.random([100, 100], -1, 1)
// Define the tests to be performed
const testCases = [
{ name: 'abs', run: () => math.abs(matrix) },
{ name: 'map typed', run: () => math.map(matrix, math.abs) },
{ name: 'map func', run: () => math.map(matrix, Math.abs) },
{ name: 'newMap typed', run: () => newMap(matrix, math.abs) },
{ name: 'newMap func', run: () => newMap(matrix, Math.abs) }
]
// Execute the tests and store the results
const testResults = testCases.map(testCase => {
const result = runTest(testCase.name, testCase.run)
return {
name: result.name,
mean: Number(result.mean.toFixed(4)),
std: Number(result.std.toFixed(4))
}
})
// Display the results in a table
console.table(testResults)
/**
* Applies a callback function to each element of a matrix.
* @param {Array} matrix - The input matrix.
* @param {Function} callback - The callback function to apply.
* @returns {Array} - The resulting matrix.
*/
function newMap(matrix, callback) {
if (math.typed.isTypedFunction(callback)) {
let implementation = () => { throw new Error('null implementation') }
return recurseTyped(matrix)
function recurseTyped(array) {
if (Array.isArray(array)) return array.map(element => recurseTyped(element))
try {
return implementation(array)
} catch (error) {
implementation = math.typed.resolve(callback, [array]).implementation
return implementation(array)
}
}
}
else {
return recurse(matrix)
function recurse(array) {
if (Array.isArray(array)) return array.map(element => recurse(element))
return callback(array)
}
}
}
/**
* Runs a performance test for a given function.
* @param {string} testName - The name of the test.
* @param {Function} testFunction - The function to test.
* @returns {Object} - The test results.
*/
function runTest(testName, testFunction) {
const totalTrials = 100
const warmupTrials = 5
let executionTimes = []
for (let i = 0; i < totalTrials + warmupTrials; i++) {
const startTime = performance.now()
testFunction()
const endTime = performance.now()
executionTimes.push(endTime - startTime)
}
// Exclude warmup times
const validTimes = executionTimes.slice(warmupTrials)
return {
name: testName,
mean: math.mean(validTimes),
std: math.std(validTimes)
}
} |
Hi, I found some progress regarding speed.
I will try to make a PR with some improvements on the deepMap and other recursion functions in the following week. I have this running benchmark test that I'm using as a proof of concept. Works best on chromium due to |
Hi, the main idea is mostly done at. As a summary you were right from the beginning since using the right amount of arguments specially when no index is needed, speeds up not only When a second argument The current develop branch, includes only one Theoretically I would like to know your opinions |
Sorry for the frequent comments, I had some spare time and found the issue very intriguing. I made PR #3266 I will chill for a bit. |
Awesome 😎 |
Thanks to #3251 this issue is reduced significantly. As you previously mentioned, now it's a matter of implementing it not only in matrices but also arrays and collections. Even though the attempts to eliminate bottlenecks in recursion functions gave some nice theoretical results, by implementing them they didn't pan out as well as #3251. |
After extending the benchmark file
/test/benchmark/map.js
, I was triggered by an outlierabs(array)
which is more than 10x faster than the rest:The relevant code is the internal function
deepMap
and the methodDenseMatrix.map
.I think there is a bug in
deepMap
when executing on a Matrix: DenseMatrix already iterates over all items itself, so in that case there is no need to call deepMap recursively. This doesn't do much harm but it is more neat to change this to just callarray.map(callback)
in case of a Matrix, and only recurse in case of an Array.After a little experiment I saw that the cause of the performance drop in
DenseMatrix.map
is caused by creating and passing anindex
array to every callback invocation. I think we can optimize this by checking if the callback actually usesindex
, and if not, do not provide it. I think we need to introduce anrecurseNoIndex
which is used whencallback.length === 1
.I think we can best look into this after #3256 is merged since that PR refactors the functions
applyCallback
andrecurse
that needs futher changes. We also need to see how #3251 works out, since that also improves the.map
function.@dvd101x what do you think?
The text was updated successfully, but these errors were encountered: