Skip to content
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

Remove bounds checks #143

Merged
merged 2 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 85 additions & 35 deletions bitset.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,13 @@ func (b *BitSet) FlipRange(start, end uint) *BitSet {
var startWord uint = start >> log2WordSize
var endWord uint = end >> log2WordSize
b.set[startWord] ^= ^(^uint64(0) << wordsIndex(start))
for i := startWord; i < endWord; i++ {
b.set[i] = ^b.set[i]
if endWord > 0 {
// bounds check elimination
data := b.set
_ = data[endWord-1]
for i := startWord; i < endWord; i++ {
data[i] = ^data[i]
}
}
if end&(wordSize-1) != 0 {
b.set[endWord] ^= ^uint64(0) >> wordsIndex(-end)
Expand Down Expand Up @@ -427,12 +432,16 @@ func (b *BitSet) NextSet(i uint) (uint, bool) {
if w != 0 {
return i + trailingZeroes64(w), true
}
x = x + 1
x++
// bounds check elimination in the loop
if x < 0 {
return 0, false
}
for x < len(b.set) {
if b.set[x] != 0 {
return uint(x)*wordSize + trailingZeroes64(b.set[x]), true
}
x = x + 1
x++

}
return 0, false
Expand Down Expand Up @@ -516,10 +525,16 @@ func (b *BitSet) NextClear(i uint) (uint, bool) {
return index, true
}
x++
// bounds check elimination in the loop
if x < 0 {
return 0, false
}
for x < len(b.set) {
index = uint(x)*wordSize + trailingZeroes64(^b.set[x])
if b.set[x] != allBits && index < b.length {
return index, true
if b.set[x] != allBits {
lemire marked this conversation as resolved.
Show resolved Hide resolved
index = uint(x)*wordSize + trailingZeroes64(^b.set[x])
if index < b.length {
return index, true
}
}
x++
}
Expand Down Expand Up @@ -615,6 +630,12 @@ func (b *BitSet) Equal(c *BitSet) bool {
return true
}
wn := b.wordCount()
// bounds check elimination
if wn <= 0 {
return true
}
_ = b.set[wn-1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fuse those in one line?

_, _ = b.set[wn-1], c.set[wn-1]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked the output of check_bce, but yeah, should be fine.

_ = c.set[wn-1]
for p := 0; p < wn; p++ {
if c.set[p] != b.set[p] {
return false
Expand All @@ -635,9 +656,9 @@ func (b *BitSet) Difference(compare *BitSet) (result *BitSet) {
panicIfNull(b)
panicIfNull(compare)
result = b.Clone() // clone b (in case b is bigger than compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
for i := 0; i < l; i++ {
result.set[i] = b.set[i] &^ compare.set[i]
Expand All @@ -649,9 +670,9 @@ func (b *BitSet) Difference(compare *BitSet) (result *BitSet) {
func (b *BitSet) DifferenceCardinality(compare *BitSet) uint {
panicIfNull(b)
panicIfNull(compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
cnt := uint64(0)
cnt += popcntMaskSlice(b.set[:l], compare.set[:l])
Expand All @@ -664,12 +685,19 @@ func (b *BitSet) DifferenceCardinality(compare *BitSet) uint {
func (b *BitSet) InPlaceDifference(compare *BitSet) {
panicIfNull(b)
panicIfNull(compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
if l <= 0 {
return
}
// bounds check elimination
data, cmpData := b.set, compare.set
_ = data[l-1]
_ = cmpData[l-1]
for i := 0; i < l; i++ {
b.set[i] &^= compare.set[i]
data[i] &^= cmpData[i]
}
}

Expand Down Expand Up @@ -712,15 +740,24 @@ func (b *BitSet) IntersectionCardinality(compare *BitSet) uint {
func (b *BitSet) InPlaceIntersection(compare *BitSet) {
panicIfNull(b)
panicIfNull(compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
}
for i := 0; i < l; i++ {
b.set[i] &= compare.set[i]
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
if l > 0 {
// bounds check elimination
data, cmpData := b.set, compare.set
_ = data[l-1]
_ = cmpData[l-1]

for i := 0; i < l; i++ {
data[i] &= cmpData[i]
}
}
for i := l; i < len(b.set); i++ {
b.set[i] = 0
if l >= 0 {
for i := l; i < len(b.set); i++ {
b.set[i] = 0
}
}
if compare.length > 0 {
if compare.length-1 >= b.length {
Expand Down Expand Up @@ -760,15 +797,22 @@ func (b *BitSet) UnionCardinality(compare *BitSet) uint {
func (b *BitSet) InPlaceUnion(compare *BitSet) {
panicIfNull(b)
panicIfNull(compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
if compare.length > 0 && compare.length-1 >= b.length {
b.extendSet(compare.length - 1)
}
for i := 0; i < l; i++ {
b.set[i] |= compare.set[i]
if l > 0 {
// bounds check elimination
data, cmpData := b.set, compare.set
_ = data[l-1]
_ = cmpData[l-1]

for i := 0; i < l; i++ {
data[i] |= cmpData[i]
}
}
if len(compare.set) > l {
for i := l; i < len(compare.set); i++ {
Expand Down Expand Up @@ -808,15 +852,21 @@ func (b *BitSet) SymmetricDifferenceCardinality(compare *BitSet) uint {
func (b *BitSet) InPlaceSymmetricDifference(compare *BitSet) {
panicIfNull(b)
panicIfNull(compare)
l := int(compare.wordCount())
if l > int(b.wordCount()) {
l = int(b.wordCount())
l := compare.wordCount()
if l > b.wordCount() {
l = b.wordCount()
}
if compare.length > 0 && compare.length-1 >= b.length {
b.extendSet(compare.length - 1)
}
for i := 0; i < l; i++ {
b.set[i] ^= compare.set[i]
if l > 0 {
// bounds check elimination
data, cmpData := b.set, compare.set
_ = data[l-1]
_ = cmpData[l-1]
for i := 0; i < l; i++ {
data[i] ^= cmpData[i]
}
}
if len(compare.set) > l {
for i := l; i < len(compare.set); i++ {
Expand Down
81 changes: 81 additions & 0 deletions bitset_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,87 @@ func BenchmarkSparseIterate(b *testing.B) {
}
}

// go test -bench=BitsetOps
func BenchmarkBitsetOps(b *testing.B) {
// let's not write into s inside the benchmarks
s := New(100000)
for i := 0; i < 100000; i += 100 {
s.Set(uint(i))
}
cpy := s.Clone()

b.Run("Equal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.Equal(cpy)
}
})

b.Run("FlipRange", func(b *testing.B) {
s = s.Clone()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.FlipRange(0, 100000)
}
})

b.Run("NextSet", func(b *testing.B) {
s = New(100000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.NextSet(0)
}
})

b.Run("NextClear", func(b *testing.B) {
s = New(100000)
s.FlipRange(0, 100000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.NextClear(0)
}
})

b.Run("DifferenceCardinality", func(b *testing.B) {
empty := New(100000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.DifferenceCardinality(empty)
}
})

b.Run("InPlaceDifference", func(b *testing.B) {
s = s.Clone()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.InPlaceDifference(cpy)
}
})

b.Run("InPlaceUnion", func(b *testing.B) {
s = s.Clone()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.InPlaceUnion(cpy)
}
})

b.Run("InPlaceIntersection", func(b *testing.B) {
s = s.Clone()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.InPlaceIntersection(cpy)
}
})

b.Run("InPlaceSymmetricDifference", func(b *testing.B) {
s = s.Clone()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.InPlaceSymmetricDifference(cpy)
}
})
}

// go test -bench=LemireCreate
// see http://lemire.me/blog/2016/09/22/swift-versus-java-the-bitset-performance-test/
func BenchmarkLemireCreate(b *testing.B) {
Expand Down
16 changes: 16 additions & 0 deletions popcnt_19.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func popcntSlice(s []uint64) uint64 {

func popcntMaskSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] &^ m[i])
}
Expand All @@ -23,6 +27,10 @@ func popcntMaskSlice(s, m []uint64) uint64 {

func popcntAndSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] & m[i])
}
Expand All @@ -31,6 +39,10 @@ func popcntAndSlice(s, m []uint64) uint64 {

func popcntOrSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] | m[i])
}
Expand All @@ -39,6 +51,10 @@ func popcntOrSlice(s, m []uint64) uint64 {

func popcntXorSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] ^ m[i])
}
Expand Down
Loading