Skip to content

Commit 366c0b5

Browse files
committed
Use RingBuffer for hasher pool
1 parent b42ce0b commit 366c0b5

File tree

2 files changed

+42
-30
lines changed

2 files changed

+42
-30
lines changed

trie/ctrie/ctrie.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ import (
2929
"bytes"
3030
"hash"
3131
"hash/fnv"
32-
"sync"
3332
"sync/atomic"
3433
"unsafe"
3534

3635
"github.com/Workiva/go-datastructures/list"
36+
"github.com/Workiva/go-datastructures/queue"
3737
)
3838

3939
const (
@@ -42,14 +42,23 @@ const (
4242

4343
// exp2 is 2^w, which is the hashcode space.
4444
exp2 = 32
45+
46+
// hasherPoolSize is the number of hashers to buffer.
47+
hasherPoolSize = 16
4548
)
4649

50+
// HashFactory returns a new Hash32 used to hash keys.
51+
type HashFactory func() hash.Hash32
52+
53+
func defaultHashFactory() hash.Hash32 {
54+
return fnv.New32a()
55+
}
56+
4757
// Ctrie is a concurrent, lock-free hash trie. By default, keys are hashed
48-
// using FNV-1a, but the hashing function used can be set with SetHash.
58+
// using FNV-1a unless a HashFactory is provided to New.
4959
type Ctrie struct {
50-
root *iNode
51-
h hash.Hash32
52-
hMu sync.Mutex
60+
root *iNode
61+
hasherPool *queue.RingBuffer
5362
}
5463

5564
// iNode is an indirection node. I-nodes remain present in the Ctrie even as
@@ -215,20 +224,19 @@ type sNode struct {
215224
*entry
216225
}
217226

218-
// New creates an empty Ctrie, defaulting to FNV-1a for key hashing. Use
219-
// SetHash to change the hash function.
220-
func New() *Ctrie {
227+
// New creates an empty Ctrie which uses the provided HashFactory for key
228+
// hashing. If nil is passed in, it will default to FNV-1a hashing.
229+
func New(hashFactory HashFactory) *Ctrie {
230+
if hashFactory == nil {
231+
hashFactory = defaultHashFactory
232+
}
221233
root := &iNode{main: &mainNode{cNode: &cNode{}}}
222-
return &Ctrie{root: root, h: fnv.New32a()}
223-
}
234+
hasherPool := queue.NewRingBuffer(hasherPoolSize)
235+
for i := 0; i < hasherPoolSize; i++ {
236+
hasherPool.Put(hashFactory())
237+
}
224238

225-
// SetHash sets the hash function used by the Ctrie. Existing entries are not
226-
// rehashed when this is set, so this should be called on a newly created
227-
// Ctrie.
228-
func (c *Ctrie) SetHash(hash hash.Hash32) {
229-
c.hMu.Lock()
230-
c.h = hash
231-
c.hMu.Unlock()
239+
return &Ctrie{root: root, hasherPool: hasherPool}
232240
}
233241

234242
// Insert adds the key-value pair to the Ctrie, replacing the existing value if
@@ -282,11 +290,12 @@ func (c *Ctrie) remove(entry *entry) (interface{}, bool) {
282290
}
283291

284292
func (c *Ctrie) hash(k []byte) uint32 {
285-
c.hMu.Lock()
286-
c.h.Write(k)
287-
hash := c.h.Sum32()
288-
c.h.Reset()
289-
c.hMu.Unlock()
293+
hasher, _ := c.hasherPool.Get()
294+
h := hasher.(hash.Hash32)
295+
h.Write(k)
296+
hash := h.Sum32()
297+
h.Reset()
298+
c.hasherPool.Put(h)
290299
return hash
291300
}
292301

trie/ctrie/ctrie_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929

3030
func TestCtrie(t *testing.T) {
3131
assert := assert.New(t)
32-
ctrie := New()
32+
ctrie := New(nil)
3333

3434
_, ok := ctrie.Lookup([]byte("foo"))
3535
assert.False(ok)
@@ -88,10 +88,13 @@ func (m *mockHash32) Sum32() uint32 {
8888
return 0
8989
}
9090

91+
func mockHashFactory() hash.Hash32 {
92+
return &mockHash32{fnv.New32a()}
93+
}
94+
9195
func TestInsertLNode(t *testing.T) {
9296
assert := assert.New(t)
93-
ctrie := New()
94-
ctrie.SetHash(&mockHash32{fnv.New32a()})
97+
ctrie := New(mockHashFactory)
9598

9699
for i := 0; i < 10; i++ {
97100
ctrie.Insert([]byte(strconv.Itoa(i)), i)
@@ -114,7 +117,7 @@ func TestInsertLNode(t *testing.T) {
114117

115118
func TestInsertTNode(t *testing.T) {
116119
assert := assert.New(t)
117-
ctrie := New()
120+
ctrie := New(nil)
118121

119122
for i := 0; i < 100000; i++ {
120123
ctrie.Insert([]byte(strconv.Itoa(i)), i)
@@ -137,7 +140,7 @@ func TestInsertTNode(t *testing.T) {
137140

138141
func TestConcurrency(t *testing.T) {
139142
assert := assert.New(t)
140-
ctrie := New()
143+
ctrie := New(nil)
141144
var wg sync.WaitGroup
142145
wg.Add(2)
143146

@@ -167,7 +170,7 @@ func TestConcurrency(t *testing.T) {
167170
}
168171

169172
func BenchmarkInsert(b *testing.B) {
170-
ctrie := New()
173+
ctrie := New(nil)
171174
b.ResetTimer()
172175
for i := 0; i < b.N; i++ {
173176
ctrie.Insert([]byte("foo"), 0)
@@ -176,7 +179,7 @@ func BenchmarkInsert(b *testing.B) {
176179

177180
func BenchmarkLookup(b *testing.B) {
178181
numItems := 1000
179-
ctrie := New()
182+
ctrie := New(nil)
180183
for i := 0; i < numItems; i++ {
181184
ctrie.Insert([]byte(strconv.Itoa(i)), i)
182185
}
@@ -190,7 +193,7 @@ func BenchmarkLookup(b *testing.B) {
190193

191194
func BenchmarkRemove(b *testing.B) {
192195
numItems := 1000
193-
ctrie := New()
196+
ctrie := New(nil)
194197
for i := 0; i < numItems; i++ {
195198
ctrie.Insert([]byte(strconv.Itoa(i)), i)
196199
}

0 commit comments

Comments
 (0)