Skip to content

Commit 7b434bb

Browse files
authored
Merge pull request #39 from jsign/setopt
indexForBit optimization
2 parents b2c774a + d17aa97 commit 7b434bb

File tree

3 files changed

+104
-14
lines changed

3 files changed

+104
-14
lines changed

hamt_bench_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package hamt
33
import (
44
"context"
55
"encoding/hex"
6+
"fmt"
67
"math/rand"
78
"runtime"
89
"testing"
@@ -47,6 +48,40 @@ func BenchmarkSerializeNode(b *testing.B) {
4748
}
4849
}
4950

51+
type benchSetCase struct {
52+
count int
53+
bitwidth int
54+
}
55+
56+
func BenchmarkSet(b *testing.B) {
57+
kCounts := []int{1, 10, 100}
58+
bitwidths := []int{5, 8}
59+
60+
var table []benchSetCase
61+
for _, c := range kCounts {
62+
63+
for _, bw := range bitwidths {
64+
table = append(table, benchSetCase{count: c * 1000, bitwidth: bw})
65+
}
66+
67+
}
68+
r := rander{rand.New(rand.NewSource(int64(42)))}
69+
for _, t := range table {
70+
b.Run(fmt.Sprintf("%d/%d", t.count, t.bitwidth), func(b *testing.B) {
71+
ctx := context.Background()
72+
n := NewNode(NewCborStore(), UseTreeBitWidth(t.bitwidth))
73+
b.ResetTimer()
74+
for i := 0; i < b.N; i++ {
75+
for j := 0; j < t.count; j++ {
76+
if err := n.Set(ctx, r.randString(), r.randValue()); err != nil {
77+
b.Fatal(err)
78+
}
79+
}
80+
}
81+
})
82+
}
83+
}
84+
5085
func BenchmarkFind(b *testing.B) {
5186
b.Run("find-10k", doBenchmarkEntriesCount(10000, 8))
5287
b.Run("find-100k", doBenchmarkEntriesCount(100000, 8))

uhamt.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,19 @@ import (
99
// the given bit in the bitset. The collapsed array contains only one entry
1010
// per bit set in the bitfield, and this function is used to map the indices.
1111
func (n *Node) indexForBitPos(bp int) int {
12-
// TODO: an optimization could reuse the same 'mask' here and change the size
13-
// as needed. This isnt yet done as the bitset package doesnt make it easy
14-
// to do.
15-
16-
// make a bitmask (all bits set) 'bp' bits long
17-
mask := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(bp)), nil), big.NewInt(1))
18-
mask.And(mask, n.Bitfield)
19-
20-
return popCount(mask)
12+
return indexForBitPos(bp, n.Bitfield)
2113
}
2214

23-
func popCount(i *big.Int) int {
24-
var n int
25-
for _, v := range i.Bits() {
26-
n += bits.OnesCount64(uint64(v))
15+
func indexForBitPos(bp int, bitfield *big.Int) int {
16+
var x uint
17+
var count, i int
18+
w := bitfield.Bits()
19+
for x = uint(bp); x > bits.UintSize && i < len(w); x -= bits.UintSize {
20+
count += bits.OnesCount(uint(w[i]))
21+
i++
22+
}
23+
if i == len(w) {
24+
return count
2725
}
28-
return n
26+
return count + bits.OnesCount(uint(w[i])&((1<<x)-1))
2927
}

uhamt_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package hamt
2+
3+
import (
4+
"math/big"
5+
"math/bits"
6+
"math/rand"
7+
"testing"
8+
)
9+
10+
func TestIndexForBitRandom(t *testing.T) {
11+
t.Parallel()
12+
r := rand.New(rand.NewSource(int64(42)))
13+
14+
count := 100000
15+
slot := make([]byte, 32)
16+
for i := 0; i < count; i++ {
17+
_, err := r.Read(slot)
18+
if err != nil {
19+
t.Fatal("couldn't create random bitfield")
20+
}
21+
bi := big.NewInt(0).SetBytes(slot)
22+
for k := 0; k < 256; k++ {
23+
if indexForBitPos(k, bi) != indexForBitPosOriginal(k, bi) {
24+
t.Fatalf("indexForBit doesn't match with original")
25+
}
26+
}
27+
}
28+
}
29+
30+
func TestIndexForBitLinear(t *testing.T) {
31+
t.Parallel()
32+
var i int64
33+
for i = 0; i < 1<<16-1; i++ {
34+
bi := big.NewInt(i)
35+
for k := 0; k < 16; k++ {
36+
if indexForBitPos(k, bi) != indexForBitPosOriginal(k, bi) {
37+
t.Fatalf("indexForBit doesn't match with original")
38+
}
39+
}
40+
}
41+
}
42+
43+
// Original implementation of indexForBit, before #39.
44+
func indexForBitPosOriginal(bp int, bitfield *big.Int) int {
45+
mask := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(bp)), nil), big.NewInt(1))
46+
mask.And(mask, bitfield)
47+
48+
return popCount(mask)
49+
}
50+
51+
func popCount(i *big.Int) int {
52+
var n int
53+
for _, v := range i.Bits() {
54+
n += bits.OnesCount64(uint64(v))
55+
}
56+
return n
57+
}

0 commit comments

Comments
 (0)