diff --git a/inplace.go b/inplace.go new file mode 100644 index 0000000..aa648ba --- /dev/null +++ b/inplace.go @@ -0,0 +1,105 @@ +package pmap + +type InplaceMap interface { + Map + SetInplace(Key, Value) InplaceMap +} + +func (p pmap) SetInplace(k Key, v Value) InplaceMap { + root, added := inplace_insert(p.root, k, v, p.hash) + p.root = root + if added { + p.len++ + } + return p +} + +func inplace_insert(n interface{}, key Key, val Value, hashFn HashFunc) (newNode interface{}, added bool) { + if n == nil { + return leaf{key, val}, true + } + hash := hashFn(key) + var insert func(n interface{}, shift uint32) interface{} + insert = func(n interface{}, shift uint32) interface{} { + // insert returns a new node (if the node should be replaced) + // or nil (if the node is unchanged or modified in place) + + //fmt.Printf("insert %v %x %#v\n", shift, hash, n) + switch n := n.(type) { + case leaf: + if n.k == key { + // replace existing entry + added = false + return leaf{key, val} + } else if h := hashFn(n.k); h == hash { + // collision + added = true + return &collision{hash, []leaf{{key, val}, n}} + } else { + if h>>shift == hash>>shift { + panic("pmap: infinite loop in insert") + } + // not a collision, so we must still have some hash bits left + // split the trie + x := newnode(n, h, shift) + if x0 := insert(x, shift); x0 != nil { + return x0 + } + return x + } + case *node: + c := n.getNode(shift, hash, key) + if c == nil { + // new node + c = leaf{key, val} + added = true + + m := bitmask(hash >> shift) + n.bitmap |= m + i := n.index(m) + n.child = append(n.child, nil) // allocate space + copy(n.child[i+1:], n.child[i:]) // shift array up + n.child[i] = c // mutate node + } else { + c = insert(c, shift+nodeShift) + if c == nil { + return nil + } + m := bitmask(hash >> shift) + i := n.index(m) + n.child[i] = c // mutate node + } + n.check() + return nil + case *collision: + if n.hash != hash { + // not a collision, so we must still have some hash bits left + // split the trie + x := newnode(n, n.hash, shift) + if x0 := insert(x, shift); x0 != nil { + return x0 + } + return x + } + for i := range n.leaf { + if key == n.leaf[i].k { + // replace existing entry + n.leaf[i] = leaf{key, val} // mutate + added = false + return nil + } + } + // new collision + added = true + n.leaf = append(n.leaf, leaf{key, val}) // mutate + return nil + default: + panic("pmap: unhandled case in insert") + } + } + newNode = insert(n, 0) + if newNode == nil { + newNode = n + } + return +} diff --git a/inplace_test.go b/inplace_test.go new file mode 100644 index 0000000..269b282 --- /dev/null +++ b/inplace_test.go @@ -0,0 +1,49 @@ +package pmap + +import ( + "fmt" + "testing" +) + +func TestInplace(t *testing.T) { + p := New(hash).(InplaceMap) + const numElems = 100 + for i := range iter(numElems) { + p = p.SetInplace(i, i) + } + if p.Len() != numElems { + t.Fatalf("Len() = %v, want %v", p.Len(), numElems) + } + for i := range iter(numElems) { + v, ok := p.Get(i) + if v != i || !ok { + t.Errorf("Get(%d) = %v %v, want %v %v", i, v, ok, i, true) + } + } + fmt.Print(p.(pmap).stats()) +} + +func BenchmarkSetInplace(b *testing.B) { + b.Run("size=10", func(b *testing.B) { benchmarkSetInplace(b, 10) }) + b.Run("size=100", func(b *testing.B) { benchmarkSetInplace(b, 100) }) + b.Run("size=1000", func(b *testing.B) { benchmarkSetInplace(b, 1000) }) + b.Run("size=10000", func(b *testing.B) { benchmarkSetInplace(b, 10000) }) + if !testing.Short() { + b.Run("size=100000", func(b *testing.B) { benchmarkSetInplace(b, 100000) }) + } +} + +func benchmarkSetInplace(b *testing.B, numElems int) { + p := New(hash).(InplaceMap) + for i := range iter(numElems) { + p = p.SetInplace(i, i) + } + if p.Len() != numElems { + b.Fatalf("Len() = %v, want %v", p.Len(), numElems) + } + b.ResetTimer() + for i := range iter(b.N) { + k := numElems + p = p.SetInplace(k, i) + } +}