-
-
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: Allow constant fields in mutable types #11430
Conversation
cc: @carlobaldassi. I also played with making BitArrays immutable, and while it sped a few operations up, |
This seems to be done well. I think the more usual way to get this functionality is to use mutable Ref cells inside immutable objects. That is much simpler, since you only need the concept of mutability that already exists, instead of adding more features to the data model. |
Admittedly, I didn't profile where the extra time was getting spent on my attempt at doing that (see my simultaneous comment above). Perhaps it'd be worth my while to try optimizing an immutable BitArray further. |
You would need to use |
Aha, I'll give that a shot. |
Very cool. I think it would be cleaner if eventually the immutable keyword would only make the frontend rewrite the type body by adding const on every field. We would then flag the type as immutable iff all fields are const. |
@vtjnash Can we rename |
Ah, sure enough, that's much better. I don't have much more time to play with it today, but running the BitArray tests on that branch is now just slightly slower than Master. If I can hammer out a few regressions it'll probably be even better for my IntSets refactor (and DataArrays, too) since an immutable BitArray will be stored inline. I'll play with it more later. 👍 |
@JeffBezanson in a trend adopted from my work on Gtk.jl (specifically the Mutable experiment hat formed the basis for the eventual Ref PR, I tend to want to encourage users to refrain from ever referring to the concrete RefValue type an instead work with the abstract concept of a murable reference. I realize that this bucks the trend of ensure that every field is concretely typed, but I'm not convinced that is not simply a premature optimization in such cases. |
I wonder if it would be useful to dispatch if a field is const. Maybe there could be a const type, |
@vtjnash Yes there are 2 concerns: we want to use the abstract type for ccall and uses like How many kinds of references are there, really? In almost every case you want a mutable single-value cell (RefValue). The only exception seems to be |
Alright, I spent a little more time this morning comparing this approach to the immutable + Ref approach in mb/frozenbits. As far as I can tell, there's about a 3x performance regression involved in the creation of an immutable BitVector with its mutable Ref as compared to just allocating one mutable type. If that can be improved, then I think const fields may not be needed. Edit: I suppose that this is a constant overhead that is only significant on smaller BitArrays. The 3x number above was for a small BitArray of < 64 elements. With larger BitArrays the cost is overwhelmed by the array allocation. |
Yet, comparing the implementations, it seems that the const-field approach is simpler from the user's perspective. For example, in the immutable BitArray case one needs two extra parameters to the type which are essentially only an implementation detail. Plus, Performance-wise, I don't know if the regressions which @mbauman observes are solvable. However, I was thinking about code like this: B = BitArray(N)
for i in mylistofindices
x += B[i]
end i.e. without |
What would the two extra type parameters be? I don't quite get that. |
It's a just a hack to only have mutable lengths on BitVectors while concretely typing all fields. Really, this is crying for a |
Effectively, it's @generated immutable BitArray{N}
quote
chunks::Vector{UInt64}
len::$(N == 1 ? RefValue{Int} : Int)
dims::$(N == 1 ? Void : NTuple{N, Int})
...
end
end |
That's not really fair --- it's a feature that you can parameterize over whether something is a RefValue. AFAICT you can't do that with the |
That is true, I'm not selectively marking the |
@vtjnash: Fwiw, I also found the Ref vs RefValue business confusions and unintuitive when I first started playing with it. I sympathize with wanting to encourage people to program to abstractions rather than conceptions. That's why AbstractArray was originally called Tensor and AbstractVector was Vector, etc. Dense versions were called Array and DenseVector. That didn't work out well and we changed it before anyone but Jeff, Viral and I had used it. To me this Ref and RefValue naming is the same story all over again. Bottom line, I think that simple concrete names should be used for simple concrete things; programming correctly to an abstraction requires awareness and care, so having a name that reminds you of that is a good thing. |
@JeffBezanson Wouldn't the first sentence imply that the abstract type could be I've also been confused/bitten by trying to use |
You generally shouldn't need to be be coding against the subtypes of No, the existence of RefArray isn't due to how Array is implemented, it is required by the definition of |
OK, thanks Jameson. I should try and understand immutable MyType
a::Int
b::Ref{Int}
end and if you want to use (faster) concrete types you need to realize if you actually want |
there's nothing wrong with a field type of |
Really? That would be nice if they are equally economical. I'm curious - are these two are completely the same in the way the binary data is structured, the layers of pointers, etc? There's no boxing with immutable MyType1
a::Int
b::RefValue{Int}
end
immutable MyType2
a::Int
b::Ref{Int}
end |
Memory layout and boxing is not the issue, none of them are inlined and (in another word) both are boxed. |
right, but that doesn't indicate a problem. |
Even if the dynamic dispatch is always as fast, which is not always the case, it still prevent inlining and prevent LLVM from seeing the memory operation. I'll be surprised if we can make this claim (that type instability for mutable types won't cause performance issue) in general. |
Perhaps not, but you can't make that argument about type stability either, in general. As I said above in my wall of text, if the performance issue would be fixed by getting LLVM to see the memory operation, then it's presumably even better to unwrap the type earlier and avoid the extra pointer indirections (and eventually doing MemSSA and allocation-elimination in type inference should handle that automatically). Alternatively, as a Ref, you could be storing a Ptr there (so that there's no allocation on the convert pass at all, and forcing the memory management fully manual) or a RefArray (so that the memory allocation for the boxes is pooled, and memory management is partly manual). |
Right, this is much harder if the field type is not a leaf type. |
Another reason for renaming |
This likely isn't going to be the direction forward here and I'm almost certainly not going to be the one to take the next steps here so I'll close this. |
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces #11430 Co-authored-by: Matt Bauman <[email protected]>
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces #11430 Co-authored-by: Matt Bauman <[email protected]>
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces #11430 Co-authored-by: Matt Bauman <[email protected]>
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces JuliaLang#11430 Co-authored-by: Matt Bauman <[email protected]>
Mark some builtin types also, although Serialization relies upon being able to mutilate the Method objects, so we do not yet mark those. Replaces JuliaLang#11430 Co-authored-by: Matt Bauman <[email protected]>
This was a fun afternoon hack. There's still a few odd ends to wrap up, but I was surprised how easy this turned out to be. I want to test the waters here… is this something that is wanted? It would close #9448, and it trivially enables the sorts of optimizations I am seeking in #9974 if you're able to mark the field constant. Upon marking BitArray's
chunks
array as constant, it gives the same sorts of speedups I saw in #11403 (but more systematically and without going through contortions or having troubles with pointer lookups within loops — the BitArray code could be simplified more now, too).Some todos:
f() = const x
segfaults. It should probably have the same error message asf() = global x
.field[i].isconst
withinjl_compute_field_offsets
… which isn't really the right name or place to do this.Further projects:
isbits
andimmutable
, but I don't understandsrc
well enough yet to grasp what that entails.