-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Functions that produce results in pre-defined outputs #1115
Comments
One idea is to use a keyword argument A simple rewrite would be Then a more far-out enhancement would be to allow the compiler to speculatively insert
could become
|
To me, putting
Since |
Also, this by itself doesn't handle the noalias issue, although of course that's something that documentation could provide. |
I think We'd also have to decide whether |
If it's not of the right size or type, in general I'd lean towards an error---for use in kernels, it might be nice to have a guarantee that allocation won't be performed. It should be easy enough to write a wrapper around it that does the allocation, if that's the behavior someone actually wants. |
Another question: presumably |
I wrote a comment on commit fff7d42 which probably should have been here instead. It seems to me that trying to find a syntax for linear algebra operations with preallocated results is overkill. The Lapack and BLAS code already does uses preallocated resutls and I think exposing these capabilities to programmers would be best accomplished by having the immediate Julia wrappers of Lapack or BLAS subroutines use preallocated storage for the results. The user-facing Julia functions would allocate the result and the sophisticated programmer who wanted to save the |
I think we mostly agree on this point, although I think we should provide one level up that "Julia-izes" Lapack but without allocating the outputs. Right now the linalg code tree seems not to provide this reliably. While filing this issue was prompted by what I found in the linalg files (I hadn't looked at them until very recently), as a matter of "Julia programming style" it goes beyond that. I'm really thinking ahead to the day when Krys will deliver multithreadable kernels from native Julia code :-), and I want to be able to use those with minimum of reading documentation about the ccall interface to external packages. So part of the plan in filing this issue was to collect comments from existing Julia developers and to be able to direct newcomers to Julia to some document that describes why this might be important. |
Hmm what about return type declarations? Basically also pattern-match for the
Another instance is special variables, where without the distinction you don't know if you're changing it's value or locally defining it. Well not quite, because |
Return type declarations are definitely interesting and have overlap with this proposal, but also have some important differences. The most obvious is that they don't solve the problem of temporary allocation. If I want a 2x2 matrix back, but don't want the algorithm to allocate fresh memory to store it, then I have to tell it which blob of memory I want it to use. That's easy if the output is one of the inputs. |
I agree that it would be good if the user is sure that it will do |
For those who don't follow every last detail of Julia development, I realized I should give a status update: the process proposed here has been completed (some time ago) for matrix multiplication. On some not-too-small problems (20x20) we've seen 1.5-fold speed increases, in addition to the benefit of extra flexibility that it permits (e.g., writing the results directly to memory-mapped storage). The pull request referenced above for tridiagonal and Woodbury is the first case where it might be implemented for inversion, too. That pull request also contains a specific proposal (the last bullet point) that could be the basis for spreading this more widely. So, anyone who cares about this issue may want to check that discussion. |
I am working on some functionality for Toeplitz matrices where I define e.g.
for the operation For some more code context have a look at https://github.com/andreasnoackjensen/ToeplitzMatrices.jl |
Let me suggest something like:
Then possible the inplace operators |
This is a very old issue. We have settled on the |
Operations like matrix multiplication,
C = A*B
, have historically been implemented asC = mult(A,B)
, and this seems very natural. Below, I argue that there are numerous benefits to habitually writing our core algorithms asmult(C, A, B)
, whereC
is a pre-allocated output. Note that this issue goes far beyond matrix multiplication: taken to the extreme, one might say that the majority of function that produce any kind of "nontrivial" output (especially ones that allocate storage for that output) might be candidates for refactoring under this proposal. Of course, many areas of Julia already follow this paradigm, e.g., the I/O functions take as first input "io", which is a stream representing the output.Here are reasons that I think we need to consider doing things this way a matter of habit:
C = mult(A, B)
can be trivially written in terms ofmult(C, A, B)
:The converse is not true: you can't write
mult(C, A, B)
in terms ofmult(A, B)
, becausemult(A, B)
allocates the storage for its output. Putting the actual core code into the three-argument version is therefore an increase in API flexibility.The only disadvantage is lots of little "stub" functions when we also want the two-argument version to be available. But in general we have a lot of those anyway, and they are a strength of Julia.
det(A_i * B_i)
for millions of small matricesA_i
andB_i
(applications where this arises: simulations, calculations on a grid, etc.). With the two-argument form of matrix multiplication, malloc has to be called for each intermediate value. One might imagine speed improvements using a single preallocated temporary for each intermediate result. In a test of 2x2 matrix multiplication, this trivially gets you a 3-fold speed improvement. (The fact that it's not larger is a triumph of Julia's memory management.)Aside from encouraging this as a matter of programming style, let me bring up some of the issues worthy of discussion. We use the
!
convention to indicate a function that mutates its inputs, such assort!
. However, this is not exactly what's happening here: in a matrix multiplication routine,A
andB
are not being mutated. There are also subtleties: forplus(C, A, B)
it's perfectly safe to call this asplus(A, A, B)
, meaning the matrix version ofA += B
. However, you can't safely do matrix multiplication in-place: the output storage has to be independent of the inputs.In other words, we might need more conventions or notation. I'm not good at naming, but to get the discussion started, let me make a few proposals.
A. We could reserve
!
for its "purest" current meaning, things like in-placesort!
.B. We might not need special notation for a version that allows you to supply outputs, e.g., we could have both
C = plus(A, B)
andplus(C, A, B)
, where the distinction is handled by multiple dispatch. Alternatively, would it be desirable/feasible to use function names likemult=(C, A, B)
, where the=
indicates that the first (or first several) arguments are outputs?C. When functions need their output storage to be independent of their inputs, they could be given names like
mult_noalias
(following notation used in Eigen and Boost) to indicate to the user that it's a bug to call such functions like this:mult_noalias(A, B, A)
.D. Finally, there has been a proposal for operator versions like
C .= A*B
. This kind of thing is clearly a good idea. But by now it should be clear that operators are only the tip of the iceberg---this issue permeates the entire function API.References to previous related discussions:
#249
https://groups.google.com/d/topic/julia-dev/bWGWeIdhsnI/discussion
https://groups.google.com/d/topic/julia-dev/NaPxnrHtut8/discussion
Commit fc14052, which began the process for some of the linear algebra routines, but was horribly inconsistent in its naming scheme and needs fixing.
*Currently the calling function couldn't determine whether an array is in-memory or mapped to a file. This could potentially lead to problems from too much automagical behavior, when, for example, the best algorithm might depend upon the storage representation. But it should be clear that in general Julia's type system usually allows you to resolve such things very cleanly via multiple dispatch; memory-mapped arrays are a bit of an exception in this regard.
The text was updated successfully, but these errors were encountered: