josdejong / mathjs

An extensive math library for JavaScript and Node.js
https://mathjs.org
Apache License 2.0
14.41k stars 1.24k forks source link

In-place operations #1134

Closed torfsen closed 1 year ago

torfsen commented 6 years ago

Currently, if I want to add a vector y to an existing vector x and store the result in x I need to do

x = math.add(x, y);

This means that new storage has to be allocated to hold the result of the addition. I'd like to avoid that.

I suggest in-place variants of those operations where it makes sense, e.g. math.iadd:

math.iadd(x, y); // Add y to x in-place, no return value

From an implementation point of view, add(x, y) could easily be implemented via iadd by something like

function add(x, y) {
  let result = math.clone(x);
  result.iadd(y);
  return result;
}

Almost all arithmetic, bitwise, complex, logical, trigonometry and some of the matrix and set functions could benefit from an in-place variant.

josdejong commented 6 years ago

In general, I strongly prefer immutable objects and pure functions. Your proposal is exactly the opposite of that, so there must be a really good reason for it.

For matrices, reuse may seriously improve performance, which can be a valid reason. We should validate whether this is indeed the case though, and get to know how much we could gain.

From a practical point of view: it would require a huge amount of refactoring, we should keep that in mind.

torfsen commented 6 years ago

You're spot on regarding the trade-off between the benefits of immutability and improved performance. I'm from a scientific computing background, so I'll happily throw out the former for the latter. But of course you might come to a different conclusion (which is perfectly fine!).

It probably boils down to deciding whether mathjs wants to be a platform for resource-intensive computations like, e.g., NumPy for Python (with all the drawbacks that improved performance and memory usage usually has regarding usability and code readability) or if it has different goals.

AFAIK, there is currently no de-facto standard JS library for scientific computing. Besides mathjs there are some other candidates, but none of those (including mathjs) are built from the ground up for resource-intensive computations.

josdejong commented 6 years ago

Yeah, first we should do a benchmark to see how many impact it would have.

@torfsen how does numpy do this? Does numpy have such functions?

Just thinking aloud: maybe we could consider iadd like functions only for specific operations instead of for "all "functions. I'm thinking about the operators +=, -=, *=, and /=. These are operations I regularly use in loops to do cumulatives and things like that. We could implement these operators in the expression parser, and have them powered by iadd, isubtract, imultiply, and idivide.

An other direction is that we could have such functions as methods on the Matrix class but not as pure functions, i.e. methods can be mutable but functions not. There are already immutable methods on Matrix like .set(...) and .resize(...).

harrysarson commented 6 years ago

I believe a much greater improvement to performance would come from using something like numpy's ndarray to store vectors and matrices rather than javscript arrays and therefore that should be a priority.

Additionally, I would argue for more meaningful method names such as matrix.inplaceAdd or matrix.modifyAdd or even matrix.withMutation.add

harrysarson commented 6 years ago

See #760

Nekomajin42 commented 6 years ago

Please don't forget the %=!

And what do you think about the ++ and -- operators?

josdejong commented 6 years ago

@harrysarson good point. we should really first put these ideas for improvement in perspective. See also the benchmark folder, where for example the performance of the JavaScript library ndarray (using Float64Array) against other libraries like numericjs which is amazingly fast but uses nested JavaScript arrays.

torfsen commented 6 years ago

@josdejong NumPy has a complex array architecture and supports in-place updates for example via the standard operators.

To be honest I didn't even know about the ndarray JS library -- it already supports in-place operations (for example using ndarray-ops), so there might be no reason to reinvent the wheel.

josdejong commented 6 years ago

I was thinking a bit more about these operators +=, -=, *=, /=, etc. We can see these operators simply as eye-candy / compact notation: a += b is the same as a = a + b. It should not necessarily have a different implementation under the hood. So maybe we should not mix them in this discussion and keep this discussion focused on matrix operations that mutate matrices vs pure functions and immutable matrices.

Most important arguments I think are:

  1. pro: mutating the matrix itself is much faster (need to validate how much performance increase we're talking about)
  2. con: keeping data immutable and functions pure is much less sensitive to bugs in general.
gwhitney commented 1 year ago

Going to reference this in the "future of matrices" discussion #2702 so closing it.