-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New function Base.unsafe_wrap! to avoid unnecessary heap allocations #28305
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
Comments
This will break many assumptions.
No, you can use a custom array type. |
Sorry, if I overlooked this. I searched, but did not yet find a way, how a C-pointer (that is changed for every call of the Julia function) could be used as pointer in a Julia array without function unsafe_wrap!. Can you give some additional hints how a "custom array type" could be defined in this way (note, on the C-side an existing DLL is used and it is not possible to (practically) change the C-code). |
You don't. At least not as an
There are countless number of examples here. https://github.com/JuliaArrays. You should even be able to tweak https://github.com/JuliaArrays/UnalignedVectors.jl to fit your need easily. |
Thanks, I understand now: A new custom array type has to be constructed using functions |
That doesn't entirely fix the issue since then the array is missing all of the standard overloads like matrix multiplication. Specifically for Sundials, the pointers already have a compatible array via the NVector type which matches the Sundials NVector https://github.com/JuliaDiffEq/Sundials.jl/blob/master/src/nvector_wrapper.jl , but relying on only getindex/setindex! does reduce the available functionality. |
Reopened, due to the comment above |
Have you considered allocating the memory in julia and using that memory as the backing store on the c side instead, rather than trying to shove a C-managed pointer into a julia array? I agree with @yuyichao that for the latter case, a custom array type would work better. Even if you could write an |
Yes, but that's exactly what cannot be done in the linked example SciML/Sundials.jl#179 . Sundials has a buffer of arrays that it can give back. This function is given to and called by Sundials: function cvodefunjac(t::Float64,
u::N_Vector,
du::N_Vector,
funjac::FunJac)
if !(pointer(funjac.u) === __N_VGetArrayPointer_Serial(u))
#@warn "Pointer is broken to FunJac.u"
funjac.u = convert(Vector, u)
end
_u = funjac.u
if !(pointer(funjac.du) === __N_VGetArrayPointer_Serial(du))
#@warn "Pointer is broken to FunJac.du"
funjac.du = convert(Vector, du)
end
_du = funjac.du
funjac.fun(_du, _u, funjac.p, t)
return CV_SUCCESS
end The arrays Sundials has are allocated in Julia and then given to Sundials. But then CVODE can give back any of its internal arrays and it seems that these change whenever it changes the order of the method (adaptive order), so The performance issue was pretty drastic in some tested cases. For a different Sundials algorithm IDA, it only gives the users back the pointers the user used to init, so this same trick in this case made there be zero allocations and resulting in a quite large speedup. |
That just says that the array implementation isn't complete and isn't specific to the array implementation you need. With this argument you can say that any array type other than It's much more helpful to just see which operations are harder to implement for custom array type than they needed to be and improve on that rather than hacking into the base |
Okay fine, in those terms then we want a custom array type |
No that's not what you want. You want to make sure that all the functions that makes sense for both |
e.g.
No it's not. That's the whole point of abstract type. They are not there just to create a good looking tree for presentation, they are actually useful for writing generic functions that only rely on certain shared properties between types and they should certainly be used that way. There are certainly still cases where it's not expressive enough (yet) which is why more expressive variants like traits could be useful, however, in this case, |
Looks like we can make a |
A final question: In NVector of Sundials "Ref{..}" is used on the C-array pointer. If the C-array pointer is changing, I guess that Ref{..} has to be applied again on the new pointer. However, Ref{..} allocates heap memory (for a vector 32 byte, for a matrix 80 bytes on my machine). So, is there a way to avoid appyling Ref{..} on the changing C-array pointer? |
https://github.com/JuliaDiffEq/Sundials.jl/blob/master/src/wrapped_api/types_and_consts.jl#L240 This is just the C-array pointer. We can just put that in a stack-allocated struct to perform dispatches. SciML/Sundials.jl#180 |
Why do you need
And the only operation done on that |
Yes, the old |
Closing due to the comments above |
If a C-function calls many times a Julia function and passes C-arrays, for example, when performing simulation, optimization, model-based controllers etc. (e.g. this case happens with the Sundials integrators, see e.g. issue https://github.com/JuliaDiffEq/Sundials.jl/pull/179), then C-arrays must be wrapped to Julia arrays. This is performed with the existing Base.unsafe_wrap(..) function. However, this function returns a Julia array and therefore allocates heap memory. In a typical simulation with Sundials IDA or CVODE this happens, say, 1000 - 10000 times for 3 arrays leading to many small heap allocations that dominate the function evaluation cost for smaller models. This performance issue could be fixed, if the C-array pointer could be replaced in an unsafe_wrap(..)ed array:
The text was updated successfully, but these errors were encountered: