-
-
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
Make BitSet store any Int #25029
Make BitSet store any Int #25029
Conversation
2097111
to
4ac7580
Compare
4ac7580
to
951bfce
Compare
base/bitarray.jl
Outdated
start > length(B) && return 0 | ||
|
||
Bc = B.chunks | ||
bitcount(B::BitArray) = bitcount(B.chunks) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
count(B::BitArray)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even sum
although I think that's the same thing when the eltype is Bool.
base/bitset.jl
Outdated
const Bits = Vector{UInt64} | ||
const Chk0 = zero(UInt64) | ||
const NoOffset = -one(Int) << 60 | ||
# + NoOffset must be big enough to stay < 0 when add with any offset. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small enough
base/bitset.jl
Outdated
bits::BitVector | ||
BitSet() = new(sizehint!(falses(0), 256)) | ||
const Bits = Vector{UInt64} | ||
const Chk0 = zero(UInt64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need this at all, you can just use 0
everywhere. The conversion will be implicit and it should be performed at compilation time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was not really for performance, more for readability!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I would still bikeshed the name though, to me it looks like a type when capitalized...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok done, renamed to full caps, also for NO_OFFSET
.
base/bitset.jl
Outdated
sizehint!(s::BitSet, n::Integer) = sizehint!(s.bits, n) | ||
|
||
# given an integer i, return the chunk which stores it | ||
chk_indice(i::Int) = i >> 6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the next are the same as _div64
and _mod64
from "bitarray.jl". I know those names are not great, but it seems slightly silly to have two sets of nearly identical definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah right! They are like that because they evolved: first they did what get_chunks_id
do, then they were updated to work for all Int
range by promoting to Int128
, and ended up this way; but chk_indice
must use the signed shift operator, so can _div64
can be changed to use >>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, no part of the code which uses _div64
is ever supposed to get a negative integer, and it would give meaningless results anyway if it did, so >>
should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems cool but I'm a little confused. Is the premise that this generalizes the functionality of BitSet
so that it supports both positive and negative integers and therefore renames it to IntSet
, but you've left off the commit changing the name?
base/bitarray.jl
Outdated
start > length(B) && return 0 | ||
|
||
Bc = B.chunks | ||
bitcount(B::BitArray) = bitcount(B.chunks) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even sum
although I think that's the same thing when the eltype is Bool.
Yes: a set data-structures which can store any
Yes, because with this commit, github shows a whole deleted file "bitset.jl" and a whole new file "intset.jl", so it's hard to track the changes between the two. I could as well put the renaming to another PR if you think it's better. |
951bfce
to
1ff860c
Compare
I removed the renaming part fow now as it's more work when rebasing. But would there be a consensus for this rename to I addressed the comments and updated the |
There are errors on 32 bits builds, |
0620440
to
1a02226
Compare
Yes, with this change, |
I tend to agree, though under the hood this data structure is more like a |
1a02226
to
73f247e
Compare
I rebased and just add some sort of fuzzy testing comparing the result of operations on |
I also ran locally the not-merged-yet benchmark for collections, and it shows only performance improvements, except for something which compares apples and oranges (i.e. |
73f247e
to
d136201
Compare
Windows failure appears to be an LAPACK precision issue. Seems unrelated? Travis failures were due to network connection, so I restarted them. |
Pondering the name a bit more might be worthwhile: This data structure is not an efficient representation of arbitrary sets of |
|
ftfty :) As mentioned above, the name
This seems orthogonal? |
I was just saying that |
I see :). Thanks for the clarification! To clarify on my end: When I see the name |
+1 to |
On the triage call, @Sacha0 has made a compelling argument for |
Triage decision: yes to this functionality and change, but keep the name |
Would Nanosoldier be on his feet again? |
|
It looked unrelated to me too. I could also restart the build just to double check ( |
I restarted it, but it's probably overkill. |
@nanosoldier |
Sorry, so many things have changed at once in 0.7 that I haven't had a chance to keep BaseBenchmarks working. I'll try to get it back up within the next couple of days, but any help updating BaseBenchmarks would be very welcome! |
The BitVector had already started to show its limits for BitSet needs. Juggling at 3 layers with BitSet, BitVector, and Vector{UInt64} was becoming confusing at times. Updating this code to handle negative integers only increased the confusion. Some modest performance improvements (~20%) could be achieved in the process for some functions.
8cbd17e
to
f39efaf
Compare
@nanosoldier |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
f39efaf
to
6072d23
Compare
Add an offset field to BitSet acting as an infimum of stored values, which can be adjusted on-the-fly. BitSet then becomes able to store negative integers, and doesn't have to have elements centered around 0.
The 1-based indexing was forcing to promote Int to Int128 in chk_indice & chk_offset, to allow correct computation with extreme values (e.g. BitSet([typemin(Int)])). 1-based indexing was a left-over from the former BitVector as underlying implementation. Switching to 0-based indexing allows both a more-direct mapping between the model and the implementation, and better performance.
When a BitSet is empty, its offset must be initialized. But checking for emptyness in each invocation of _setint! was costly, so we put the check in a branch which is called less often.
_setint! was delegating to the bitarray code, but then some work was being redone in get_chunks_id. So we split this functionality into lower level functions.
* isempty: using _check0 instead of all makes it 35% faster * ==: checking first non-overlapping parts is more likely to be faster, as the the lower and upper parts of the bits field are unlikely to be zero (at least for a freshly created BitSet)
6072d23
to
55852ee
Compare
For benchmarks results see my comment above for the expected "regression" for |
Thanks again for this beautiful work @rfourquet! :) |
Although I had started this work months ago, it's because I didn't like the new name that I felt the urge to finish it before feature frease, to give an argument to change it back... here we are now with the same name, but I guess you are responsible for we having this feature now :) |
Some of these commits should have been squashed. |
These are all pretty self contained and "clean" (not of the "fix typo" or "wip" kind), so I though it would be OK, but sorry about that, will try to squash more next time. |
BitSet
was renamed toIntSet
a couple months ago (#24282) because it couldn't store negative integers. This PR implements this feature, and renamesBitSet
back toIntSet
.It is implemented as a kind of offset array, so it's purpose is still to store a dense set of integers, but those don't have to be grouped around 0. For example,
IntSet(typemin(Int):typemin(Int)+100)
is perfectly fine.One notable change: using a
BitVector
as the underlying implemention started to get in the way: its API was insufficient, and it was tedious to play with and maintain the code at 3 layers simultaneously (IntSet
,BitSet
,Vector{UInt64}
). So thebits
field ofIntSet
is now directly aVector{UInt64}
, with calls to low-level bitarray.jl functions.IntSet
being used in inference.jl, I knew I had to keep good performance, so there is a small series a commits which each implement some improvements, the end result looks very satisfying e.g.The last example shows that there are some non-negligible regressions too,I didn't investigate thoroughly the performance though (BTW, there is a BaseBenchmarks.jl PR to be soon merged which tracks some of the perfs), and more improvements can be obtained. Obviously, the oldBitSet
will always be more performant than thisIntSet
, if given the same set of optimizations, but I would say by no more than roughly 15% in general. I think it's good enough for what this gives us.PS: Because GitHub doesn't seem to handle nicely file renames, I temporarily reverted the last commit which was deprecating
BitSet
->IntSet
, to make reviewing easier.