-
-
Notifications
You must be signed in to change notification settings - Fork 180
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
NextSetMany is broken and probably not a good idea. #65
Comments
Let us try it out... Here is your function... func good(set []uint64) (checksum uint) {
for wordIdx, word := range set {
var wordIdx = uint(wordIdx * 64)
for word != 0 {
var bitIdx = uint(trailingZeroes64(word))
word ^= 1 << bitIdx
// Do something with the result of the next line
var index = wordIdx + bitIdx
checksum += index
}
}
return checksum
} Here is a related benchmark... func BenchmarkFlorianUekermannIterateManyComp(b *testing.B) {
var input = make([]uint64, 68)
setRnd(input, 4)
b.ResetTimer()
var checksum = uint(0)
for i := 0; i < b.N; i++ {
checksum += good(input)
}
if checksum == 0 { // added just to fool ineffassign
return
}
} Here is the version with our current API: func BenchmarkFlorianUekermannIterateMany(b *testing.B) {
var input = make([]uint64, 68)
setRnd(input, 4)
var bitmap = From(input)
b.ResetTimer()
var checksum = uint(0)
for i := 0; i < b.N; i++ {
buffer := make([]uint, 256)
var last, batch = bitmap.NextSetMany(0, buffer)
for len(batch) > 0 {
for _, idx := range batch {
checksum += idx
}
last, batch = bitmap.NextSetMany(last+1, batch)
}
}
if checksum == 0 { // added just to fool ineffassign
return
}
} Here are the result on a Linux server...
Here are the results on my laptop...
So according to these tests, your version is systematically slower.
Pull request invited. Please also provide benchmarks showing that your code is faster. |
Please upgrade to Version 1.1.6 the latest version. |
You'll want to test larger bitsets as well as lower densities. 68 and 4 were chosen because that's where the bug happens. This could be anything. It also seems like you replaced the I won't send a PR, since we aren't using this library. I just stumbled over the bug while checking the claim about batching. I'll retest with the fixed code, but I doubt it makes a big difference. |
In the lastest version, I have added tests at various densities. When the bitset is super low-density, the bulk of the time is just spent scanning zero words (arguably it is indicative that you shouldn't be using a bitset) so all methods are pretty much equivalent. Otherwise, the Surprisingly (to me),
|
I don't have a shorter example unfortunately. This example will loop indefinitely:
My benchmarks indicate that NextSetMany is conceptually a bad idea as well. The following is depending on the circumstances (bitset length and density of ones) equally fast or faster:
I'm sure you can turn this in an iterator without too much effort, or just take a function pointer. Any serious work to be done will dwarf the overhead of the function pointer or iterator call anyway. In particular in the case with many zeros (which where the NextSet functions are useful) the relatively naive function above is much faster.
Note that the above is a fairly naive implementation, which is nowhere close to optimal. There is at the very least a superfluous
word ==0
check inbits.TrailingZeros64
which could be skipped. But I suspect there are even more gains to be had with a little effort.The text was updated successfully, but these errors were encountered: