-
-
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
A new "outer_similar" #22218
Comments
Doesn't this use case require a mutable array? If so, then the fact that |
Another spelling is |
No. It's perfectly fine to iteratively update the values of a type regardless of the implementation detail of mutable or immutable. Simple case: initial value mutable struct MyType{T<:Number,T2<:AbstractArray}
a::T
u::T2
end You can do something like: function evolve!(mt::MyType{T,T2}) where T where T2<:AbstractArray{T3} where T3<:Union{Number,SArray} # Immutable dispatch, since there's no immutable trait
mt.u = mt.a .* mt.u .+ 1
end
function evolve!(mt::MyType)
mt.u .= mt.a .* mt.u .+ 1
end so that way all work with the reasonable interpretation: mt = MyType(2.0,2.0)
mt = MyType(2.0,[2.0])
mt = MyType(2.0,@SVector([2.0])) This is essentially a minimal example for diffeq, rootfinding, iterative numerical linear algebra solvers, optimization, and nerual nets BTW. Most packages don't have the extra code path for immutables since it can lead to a lot of code duplication, but it can be very useful if Now that example works with "standard numbers", but if you do this with unitful numbers it can fail due to |
I don't quite see how a |
For the mutable struct MyType{T<:Number,T2<:AbstractArray}
a::T
u::T2
u2::T3
end Assume you want to start with mt = MyType(2.0,2.0)
mt = MyType(2.0,[2.0])
mt = MyType(2.0,@SVector([2.0])) to work? If you use As mentioned earlier, the generic way to handle the units here would be to initialize with |
Yes, |
Ah, so it's a placeholder value that will never be read? I guess you could leave the field undefined at first. That's a bit unsatisfying, but creating values intended not to be used seems pretty marginal to me as well. It's not totally clear to me if you want a copy, or just a new array with undefined or zero'd contents. If a copy, then |
That completely depends on the application. But it's still all the same: an abstraction for "get me the same kind of array, but change the element type to
That might actually be a good solution. |
Nevermind: using GPUArrays
u0 = GPUArray(rand(Float32, 32, 32))
Float64.(u0) errors, while |
For the case of julia> sima(dims) = (a = Array{Float64}(dims); sum(a))
sima (generic function with 1 method)
julia> sims(dims) = (a = SharedArray{Float64}(dims); sum(a))
sims (generic function with 1 method)
julia> using BenchmarkTools
julia> @benchmark sima((3,3)) seconds=1 gcsample=true
BenchmarkTools.Trial:
memory estimate: 160 bytes
allocs estimate: 1
--------------
minimum time: 38.926 ns (0.00% GC)
median time: 39.664 ns (0.00% GC)
mean time: 40.159 ns (0.00% GC)
maximum time: 41.671 ns (0.00% GC)
--------------
samples: 5
evals/sample: 434
julia> @benchmark sims((3,3)) seconds=1 gcsample=true
BenchmarkTools.Trial:
memory estimate: 13.34 KiB
allocs estimate: 349
--------------
minimum time: 899.431 μs (0.00% GC)
median time: 937.509 μs (0.00% GC)
mean time: 948.774 μs (0.00% GC)
maximum time: 1.021 ms (0.00% GC)
--------------
samples: 4
evals/sample: 1 We create a lot of arrays for temporary purposes, and that performance gap is so extraordinary that it makes a certain amount of sense to say "if you want a shared array, you'd better create it explicitly." I also agree that the alternative makes sense; with our current API I am not sure there's a good answer here. |
Is there any good reason for |
While trying to get generic codes working for
AbstractArray
s, I realized that there's a pretty crucial abstraction missing: the ability to create the same array type with a different element type.There is a distinction to be made here.
copy(A)
creates a copy, so its type is the same. You cannot change the element type. Using the constructors can require different syntax for eachAbstractArray
for clear reasons.Now, the clear way to go seems to be
similar
, butsimilar
is not guaranteed to return the same outer array type. For one thing, it returns asimilar
mutable array type, so asimilar(::SArray)::MArray
. In addition,similar(::SharedArray)::Array
for a reason that doesn't make much sense to me, but maybe that's because I am usingsimilar
differently:#5893
#13731
Even here @tkelman noted:
So I am proposing that we have a new function like
similar
which guarantees that the returned array matches in the outer type. Whileouter_similar(A)
is essentially a copy, the main usages being forouter_similar(A,T)
to change the element type andouter_similar(A,T,indices...)
to change the size can be very useful in generic codes regardless of the arrays used.Example
This comes up in many areas, and areas I have experienced it lately include DiffEq, numerical linear algebra, rootfinding, and neural nets. Essentially, the library code can be written all in terms of broadcasted and matrix operations. These scientific computing algorithms don't actually need to make use of indexing at all outside of a user's function: they just need to do "array-wide" operations. So as long as they can properly allocate arrays of the right type for their
A .= B.*C
type of algorithms, they will generally work on all of these array types (even "weird arrays" likeGPUArray
s which don't even have indexing defined).Assume
C
is the user's input array that you want to match types with. Without this proposedouter_similar
, there is no good way to define the pre-allocatedA
in a way that you know it matches the array types of the user input, sincesimilar
can change things. In simple algorithms, one can get by withcopy
. But if you want the algorithm to be unit-safe, for example here you would want to defineB
to match the user's input array but be unitless. In this case you would want toB=similar(C,T)
whereT
is the unitless element-type ofA
. But this is where we run into the issue thatsimilar
isn't guarenteed to return the same type. If you didA = copy(C); B = similar(C,T)
, now you have twoSharedArray
s and anArray
, or things like that. For StaticArrays, this is also an issue if you want to make an initialB
in a mutable type which you update later, since this will silently change to anMArray
instead of anSArray
and worsen the performance (though in that case we will need to useA = B.*C
, but that's a different issue). If we want this broadcast to be automatically distributed or parallel, this type of type-changing will cause "auto-parallelism of generic algorithms to fail".This makes me happy because Julia is so so so close to having auto-parallelism of generic algorithms work, as shown by tests on DiffEq (SciML/DifferentialEquations.jl#176).
Current Workaround
There is a current workaround in some cases. You can pre-allocate and change types using broadcast. Want to make a unitless version?
B = C./C
. Need to promote the types?B = C .+ D
. But this is an overly expensive way to get around the fact thatsimilar
is not the right abstract for this job.I am open to ideas for a different name than
outer_similar
. It's bad, but I thought writing down this argument was better than waiting for a good name.The text was updated successfully, but these errors were encountered: