-
-
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
WIP: implementation of fixed-size arrays #7568
Conversation
This creates both mutable and immutable versions
While I really would love to get this addressed, I still think that it is crucial that the memory layout is tight. Unless this is solved there would be no way around ImmutableArrays.jl for me. The use case I have is the representation of a vector field. This could be done by |
I'm very tempted to use this instead of ImmutableArrays, as the dimension is part of the type parameter, which would make thinks a lot easier for me. Especially for having generic geometry types which work for 2D/3D/4D. #after warmup:
elapsed time: 0.326852152 seconds (128000144 bytes allocated, 16.11% gc time) # Array
elapsed time: 0.732040295 seconds (504000520 bytes allocated, 28.74% gc time) # FixedArrayI
elapsed time: 0.357538889 seconds (144000160 bytes allocated, 14.63% gc time) # FixedArrayM
elapsed time: 0.004746544 seconds (16 bytes allocated) # ImmutableArrays So the question is: is there, besides what you already mentioned, a reason that FixedArrays will never be as fast as ImmutableArrays? I'm poking a little bit in the dark here, as I'm not really familiar with the underlying problems;) |
By the way, how do I get a pointer to a FixedArray, without accessing data? |
Can you post your test code? I don't get the same results as you, and I'm wondering if something is getting compiled out in the test of ImmutableArrays. Here's my test: using ImmutableArrays
function testimmut(A, B, n)
local C
for i = 1:n
C = A*B
end
C
end
function testmut(A, B, n)
C = A*B
for i = 1:n
A_mul_B!(C, A, B)
end
C
end
A = rand(1:10, 2, 3)
B = rand(1:10, 3, 3)
IA = Matrix2x3(A)
IB = Matrix3x3(B)
testimmut(IA, IB, 1)
println("ImmutableArrays")
@time testimmut(IA, IB, 10^6)
FMA = FixedArrays.FixedArrayM(A)
FMB = FixedArrays.FixedArrayM(B)
testmut(FMA, FMB, 1)
println("FixedM, mutating & preallocated")
@time testmut(FMA, FMB, 10^6)
testimmut(FMA, FMB, 1)
println("FixedM, allocating")
@time testimmut(FMA, FMB, 10^6) And the results:
So you can see it's very competitive if you don't have to allocate the output. Regarding the pointer and array-of-fixedarrays, these don't work like you want them to yet. That's the main problem here. See comment by @tknopp above, which is indeed the main reason this can't go forward yet. |
I used your version of the function, which does the allocation differently: From what I read, I conclude that the biggest thing missing in I'm still a big fan, of defining the fixed size arrays in an abstract way. It seems, it is very similar to what @timholy is already doing, but just to make sure I understand things correctly, here is what I would like to have: abstract AbstractFixedArray{T,N,SZ} <: DenseArray{T,N}
#some convenient type aliases:
typealias Vector3{T} AbstractFixedArray{T,1,(3,)}
Base.size{T,N,SZ}(x::AbstractFixedArray{T,N,SZ}) = SZ
Base.ndims{T,N,SZ}(x::AbstractFixedArray{T,N,SZ}) = N
Base.length{T,N,SZ}(x::AbstractFixedArray{T,N,SZ}) = prod(SZ)
Base.eltype{T,N,SZ}(x::AbstractFixedArray{T,N,SZ}) = T
# This should rather be implemented like getfield, to get O(1)
Base.getindex{T,N,SZ}(x::AbstractFixedArray{T,N,SZ}, key::Integer) = (key == 1 ? x.x : (key == 2 ? x.y : (key == 3 ? x.z : error("outofbounds")))) # very slow?
# Some custom functions to speed up particular arithmetic operations.
# Which hopefully some people with SIMD knowledge will supply
.*{T <: Vector3}(a::T, b::T) = T(a[1]*b[1], a[2]*b[2], a[3]*b[3])
# Now you can just define your own fixed size array, like this:
immutable MyFixed{T} <: Vector3{T}
x::T
y::T
z::T
end It would also avoid the problem, of how to name the dimensions, as you can just name them yourself. Base then just needs to offer the most common ones, like in I just did a very hasty research about other languages and fixed size arrays with tight memory layout. Seems, if we do this in an elegant way, Julia could quite easily take the lead in supplying the nicest to use fixed size array =) Or do you know a language where you could do stuff like this: immutable MyFeatureVector{T} <: FixedVector4{T}
atmosphericpressure::T
temperature::T
altitude::T
latitude::T
end
data = MyFeatureVector[.........]
kmeans(data) # if kmeans is defined as kmeans{T <: Real}(data::Array{FixedVector{T, D}})
ccall(....., data) #yay, tight! |
Hi,
I haven't implemented any mathematical operations yet, as I don't have time and first want to discuss this with you. Well here is the code: This concept introduces more semantics and fits nicely with multiple dispatch. |
@SimonDanisch: I think one issue is that your implementation is not generic enough. One wants at least sizes from 2-4 and dimension 1-2 (vector,matrix). ImmutableArrays achieves this through macros and using fields. It would be even better to have a generic |
Well that's what I have right? It's definitely not perfect, as you need macros to have a sensitive amount of predefined vectors/matrixes. But at least you can write functions, which take abstract types of the form |
Ah ok that is indeed nice to have this abstract layer that allows to implement the functions in a generic way without extra macro. Currently you have, however, still the Tuples would not give named field axes. But I am not sure how important that is. |
Well with types, you have to have it like this, but at least, it gets propagated to the abstract type. |
Hm, I think the requirement of named field access conflicts a little with making this generic. In #5857 this has not been seen as a requirement. It might be indeed good to have the abstraction layer for this reason. With #1974 one might implement everything in a single generic type. Actually this use case is very interesting for #1974. I think your suggestion to get something in base with the right interface and later exchange the implementation with tuple-based version seems interesting. The only question is whether this could not live in a package for the moment. @Keno: Is the "tight memory tuple" something that is planned for 0.4 or is this way to early? Thanks. |
The good thing about an abstraction layer is, that there is no real conflict between these different concepts, as long as the abstract mathematical operations are defined properly. immutable BenchmarkResult{T} <: FixedVector{T,3}
time::T
gctime::T
memory::T
end
# vs:
immutable BenchmarkResult{T} <: FixedVector{T,3}
data::NTuple{3,T}
end
Base.getfield(x::BenchmarkResult, f::Field{:time} ) = x.data[1]
Base.getfield(x::BenchmarkResult, f::Field{:gctime} ) = x.data[2]
Base.getfield(x::BenchmarkResult, f::Field{:memory} ) = x.data[3]
benchresults = BenchmarkResult[...]
#some statistics
mean(benchresults)
... |
Sorry this took me so long to get to. The idea of having a generic abstract wrapper, backed by specific instance types that the user doesn't actually have to know about or use, is actually quite interesting. To, me, perhaps the most interesting issue would be: could you cleanly autogenerate types to arbitrary sizes? Clang has to do this to match C structs: see examples here, quite amazing the lengths that it has to go to in order to construct objects with a If that could happen behind the scenes in a dynamic way, it seems pretty attractive in that it uses technologies we have now. #7941 contains lists of things we don't have now, but would certainly make this easier. |
Here is my take:
But actually it would also be very nice to have some syntax to construct fixed size arrays. If we would define arithmetics on tuples this would be one way (although it would be restricted to 1D arrays) |
You can rename types with |
But with |
Yes! Sorry for the confusion, I shouldn't have called it renaming! |
Sorry to continue the conversation with myself here, but if you use your idea of generating different concrete instantiations with one abstract wrapper type, won't you get this for free? As I understand it, your suggestion is that a typealias FixedSizeVector{T,L} FixedSizeArray{T,1,(L,)}
immutable Vec3{T} <: FixedSizeVector{T,3}
x::T
y::T
z::T
end so you could use either |
You can for example implement a generic, differently named vector. Also the more esoteric field accessors can't be implemented with an immutable type. So what I proposed should work better together with a generic implementation, and gives you more options. typealias FixedSizeVector{T,D} AbstractFixedSizeArray{T, (D,), 1}
typealias FixedSizeMatrix{T, RD, CD} AbstractFixedSizeArray{T, (RD, CD), 2}
immutable Vec{T, D} <: FixedSizeVector{T,D}
::FixedSizeArray{T, (D,), 1}
end
immutable Mat{T, RD, CD} <: FixedSizeMatrix{T, RD, CD}
::FixedSizeArray{T, (RD, CD), 2}
end
#Assuming you have the right constructors, this would be possible:
(*)(a::FixedMatrix{T, N, M}, b::FixedMatrix{M, P})
BaseType = typeof(a) # Maybe assert, that a and b are of the same base type
....
return BaseType{T, N,P}(result)
end |
This is actually also a very clean signature for matrix multiplication, as it exactly describes what's happening: immutable FixedArrayWrapper{T, D, N} <: AbstractFixedArray{T, D, N}
data::Array{T, N}
end
function (*)(a::Matrix{T}, b::Matrix{S})
return (FixedArrayWrapper(a) * FixedArrayWrapper(b)).data
end
function (*)(a::FixedMatrix{T, N, M}, b::FixedMatrix{T, M, P})
# code for multiplication of matrices with well suited dimensions
end
function (*)(a::FixedMatrix, b::FixedMatrix)
# code for multiplication of matrices with arbitrary dimensions
# which usually throws an error
end |
I would mostly say, go for it and see how it works out. |
Well CartesianIndex seems to be a perfect example of a type, that would profit from the infrastructure I have in mind. immutable CartesianIndex{N} <: NTuple{N, Int} Implementation wise, I went down a pretty similar road like CartesianIndex for my newest iteration, so in principle it shouldn't be hard too generalize this and put things under the same interface. |
I guess @tknopp is right with saying, that we have everything needed to implement the abstract interface (at #9821) abstract RGB{T} <: FixedSizeVector{T, 3}
immutable Red{T} <: Dimension{T}
val::T
end
immutable Green{T} <: Dimension{T}
val::T
end
immutable Blue{T} <: Dimension{T}
val::T
end
@accessors RGB (Red => 1, Green => 2, Blue => 3)
@show a = RGB(0.1f0,0.1f0,0.3f0) # FSRGB{Float32}(0.1f0,0.1f0,0.3f0)
@show a[Green] # Green{Float32}(0.1f0)
@show a[2] # 0.1f0
@show a + a # FSRGB{Float32}(0.2f0,0.2f0,0.6f0)
Implementation: https://gist.github.com/SimonDanisch/2855688b465ac9143b21#file-fsarray-jl |
two things are wrong with this: immutable RGB{T} <: FixedSizeVector{T, 3}
r::T
g::T
b::T
end There was a second shortcoming with which I came up with yesterday night, but I seem to have forgotten it today :D |
Okay, clearer argument, of why it's not that easy to cleanly switch out the immutable implementation with NTuples at some point. I've two demands for the current implemenation:
So if people start to have this code in their package: immutable RGB{T} <: FixedSizeVector{T, 3}
r::T
g::T
b::T
end From what I know, this would mean they wouldn't be able to have their code translate to valid SPIR. 1#Without NTuple
immutable RGB{T} <: FixedSizeVector{T, 3}
data::GenericFixedVector3{T}
end
#When NTuple replaces the generic fixed size vector
immutable RGB{T} <: FixedSizeVector{T, 3}
data::NTuple{3, T}
end Which means the currently developed Api needs to work with this kind of wrapper implementation, which is not too hard. 2At some point, we restructure 3Probably the best way to go for now is, to use a macro for the type creation, which can silently change the type creation process, while the FixedSizeVector implementation evolves. @FixedSizeVector begin Name{T} # For a Vector only defined on one N like RGB
field1::T
field2::T
end
@FixedSizeVector Name{T, N} # For variable Size vectors like Vector/1/2/3/4 |
In the case of option 3, I guess the macro could also pick up on Proposal 3 is very close in spirit the generic request of a "staged type" implementation #8472 , which could for now be implemented using a macro that creates an abstract type (e.g. |
By the way, mutability is still not supported. I got a little bit lost on the progress on this. I just now it should be theoretically possible to have mutability + dense memory layout. Is it possible while keeping stack allocation, though? I would think it is, as long as the pointer gets destroyed together with the immutable?! That's the implementation I would go for right now! Its not nice but does it's job I suppose. So, we could settle for something along these lines for now: @FixedSizeVector begin Name{T} # For a Vector only defined on one N like RGB
field1::T
field2::T
end
# -> yields a custom immutable with exactly these fields
@FixedSizeVector Name{T,2} # -> uses the generic fsvectors
@FixedSizeVector Name{T, N} # -> abstract + generic fsvectors I'm still hoping, that someone jumps in with something more elegant ;) Would be nice if all involved people could state their agreement/disagreement, so it's not just hanging in the air! |
This is a high-level comment that may be out of place: Do you really need mutability? To update one element, you could create a new vector from the old vector where this element has been replaced (a merge operation). Then you assign this new vector to the old variable. LLVM should optimize this. I don't think pointers should factor into this. |
Trying to write some code with |
I know, this works pretty well, and I've done well with immutables so far. I have two arguments for mutability, though: |
I'm slowly getting somewhere:
|
By the way, my fixedsizearray implementation is slowly becoming functional! Best, |
That's exciting news! |
Yes! Do I understood correctly that "smoothly" would mean that soon something like |
Indirectly, yes. This already works with the current implementation( it
|
Yay!
Looking forward for an update! |
Superseded by the FixedSizeArrays package. |
Is there a way to post a link to an issue/PR without creating a github reference? Creating the reference above was accidental. |
@JaredCrean2 I wouldn't worry about it. But presumably you could use a url shortner, I suspect that github doesn't expand. test: https://goo.gl/2EI6e2 |
Success, no reference created. Thanks. |
This implements fixed-size arrays (whose size is part of the type parameters). Compared to the proof-of-concept in #5857, it solves the constructor problems. There was some debate in #5857 (and echoed on julia-dev) about whether mutable or immutable would be more desirable, so for fun this implements both.
Ultimately, the multiplication routine will be a nice application of staged functions. As it is, this defines 128 new methods for
*
.This has uncovered some type-inference problems, to be filed in other issues. Given that I'd expect such problems to destroy performance, it's shockingly good. (Of the fixed-size tests, only the last line below does not suffer from type-stability problems. Hence, it is more illustrative of what one might ultimately hope for.)
Timing info: