diff --git a/server/stree/dump.go b/server/stree/dump.go index 4a7d76fb586..60f03e4aad1 100644 --- a/server/stree/dump.go +++ b/server/stree/dump.go @@ -51,6 +51,7 @@ func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) { func (n *leaf[T]) kind() string { return "LEAF" } func (n *node4) kind() string { return "NODE4" } func (n *node16) kind() string { return "NODE16" } +func (n *node48) kind() string { return "NODE48" } func (n *node256) kind() string { return "NODE256" } // Calculates the indendation, etc. diff --git a/server/stree/node16.go b/server/stree/node16.go index 7da5df89d99..c0c12aafd57 100644 --- a/server/stree/node16.go +++ b/server/stree/node16.go @@ -50,7 +50,7 @@ func (n *node16) findChild(c byte) *node { func (n *node16) isFull() bool { return n.size >= 16 } func (n *node16) grow() node { - nn := newNode256(n.prefix) + nn := newNode48(n.prefix) for i := 0; i < 16; i++ { nn.addChild(n.key[i], n.child[i]) } diff --git a/server/stree/node256.go b/server/stree/node256.go index f5bf69bc93c..5d08b1487ab 100644 --- a/server/stree/node256.go +++ b/server/stree/node256.go @@ -51,10 +51,10 @@ func (n *node256) deleteChild(c byte) { // Shrink if needed and return new node, otherwise return nil. func (n *node256) shrink() node { - if n.size > 16 { + if n.size > 48 { return nil } - nn := newNode16(nil) + nn := newNode48(nil) for c, child := range n.child { if child != nil { nn.addChild(byte(c), n.child[c]) diff --git a/server/stree/node48.go b/server/stree/node48.go new file mode 100644 index 00000000000..86b4c824a7e --- /dev/null +++ b/server/stree/node48.go @@ -0,0 +1,110 @@ +// Copyright 2023-2024 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stree + +// Node with 48 children +// Memory saving vs node256 comes from the fact that the child array is 16 bytes +// per `node` entry, so node256's 256*16=4096 vs node48's 256+(48*16)=1024 +// Note that `key` is effectively 1-indexed, as 0 means no entry, so offset by 1 +// Order of struct fields for best memory alignment (as per govet/fieldalignment) +type node48 struct { + key [256]byte + meta + child [48]node +} + +func newNode48(prefix []byte) *node48 { + nn := &node48{} + nn.setPrefix(prefix) + return nn +} + +func (n *node48) addChild(c byte, nn node) { + if n.size >= 48 { + panic("node48 full!") + } + n.child[n.size] = nn + n.key[c] = byte(n.size + 1) // 1-indexed + n.size++ +} + +func (n *node48) findChild(c byte) *node { + i := n.key[c] + if i == 0 { + return nil + } + return &n.child[i-1] +} + +func (n *node48) isFull() bool { return n.size >= 48 } + +func (n *node48) grow() node { + nn := newNode256(n.prefix) + for c := byte(0); c < 255; c++ { + if i := n.key[c]; i > 0 { + nn.addChild(c, n.child[i-1]) + } + } + return nn +} + +// Deletes a child from the node. +func (n *node48) deleteChild(c byte) { + i := n.key[c] + if i == 0 { + return + } + i-- // Adjust for 1-indexing + last := byte(n.size - 1) + if i < last { + n.child[i] = n.child[last] + for c := byte(0); c <= 255; c++ { + if n.key[c] == last+1 { + n.key[c] = i + 1 + break + } + } + } + n.child[last] = nil + n.key[c] = 0 + n.size-- +} + +// Shrink if needed and return new node, otherwise return nil. +func (n *node48) shrink() node { + if n.size > 16 { + return nil + } + nn := newNode16(nil) + for c := byte(0); c < 255; c++ { + if i := n.key[c]; i > 0 { + nn.addChild(c, n.child[i-1]) + } + } + return nn +} + +// Iterate over all children calling func f. +func (n *node48) iter(f func(node) bool) { + for _, c := range n.child { + if c != nil && !f(c) { + return + } + } +} + +// Return our children as a slice. +func (n *node48) children() []node { + return n.child[:n.size] +} diff --git a/server/stree/stree_test.go b/server/stree/stree_test.go index 7421bcf6314..e6435b08c87 100644 --- a/server/stree/stree_test.go +++ b/server/stree/stree_test.go @@ -78,7 +78,6 @@ func TestSubjectTreeNodeGrow(t *testing.T) { require_False(t, updated) _, ok = st.root.(*node16) require_True(t, ok) - // We do not have node48, so once we fill this we should jump to node256. for i := 5; i < 16; i++ { subj := b(fmt.Sprintf("foo.bar.%c", 'A'+i)) old, updated := st.Insert(subj, 22) @@ -89,6 +88,20 @@ func TestSubjectTreeNodeGrow(t *testing.T) { old, updated = st.Insert(b("foo.bar.Q"), 22) require_True(t, old == nil) require_False(t, updated) + _, ok = st.root.(*node48) + require_True(t, ok) + // Fill the node48. + for i := 17; i < 48; i++ { + subj := b(fmt.Sprintf("foo.bar.%c", 'A'+i)) + old, updated := st.Insert(subj, 22) + require_True(t, old == nil) + require_False(t, updated) + } + // This one will trigger us to grow. + subj := b(fmt.Sprintf("foo.bar.%c", 'A'+49)) + old, updated = st.Insert(subj, 22) + require_True(t, old == nil) + require_False(t, updated) _, ok = st.root.(*node256) require_True(t, ok) } @@ -160,13 +173,13 @@ func TestSubjectTreeNodeDelete(t *testing.T) { require_Equal(t, *v, 22) _, ok = st.root.(*node4) require_True(t, ok) - // Now pop up to node256 + // Now pop up to node48 st = NewSubjectTree[int]() for i := 0; i < 17; i++ { subj := fmt.Sprintf("foo.bar.%c", 'A'+i) st.Insert(b(subj), 22) } - _, ok = st.root.(*node256) + _, ok = st.root.(*node48) require_True(t, ok) v, found = st.Delete(b("foo.bar.A")) require_True(t, found) @@ -176,6 +189,22 @@ func TestSubjectTreeNodeDelete(t *testing.T) { v, found = st.Find(b("foo.bar.B")) require_True(t, found) require_Equal(t, *v, 22) + // Now pop up to node256 + st = NewSubjectTree[int]() + for i := 0; i < 49; i++ { + subj := fmt.Sprintf("foo.bar.%c", 'A'+i) + st.Insert(b(subj), 22) + } + _, ok = st.root.(*node256) + require_True(t, ok) + v, found = st.Delete(b("foo.bar.A")) + require_True(t, found) + require_Equal(t, *v, 22) + _, ok = st.root.(*node48) + require_True(t, ok) + v, found = st.Find(b("foo.bar.B")) + require_True(t, found) + require_Equal(t, *v, 22) } func TestSubjectTreeNodesAndPaths(t *testing.T) { @@ -341,7 +370,7 @@ func TestSubjectTreeNoPrefix(t *testing.T) { require_True(t, old == nil) require_False(t, updated) } - n, ok := st.root.(*node256) + n, ok := st.root.(*node48) require_True(t, ok) require_Equal(t, n.numChildren(), 26) v, found := st.Delete(b("B")) @@ -636,3 +665,76 @@ func TestSubjectTreeMatchAllPerf(t *testing.T) { t.Logf("Match %q took %s and matched %d entries", f, time.Since(start), count) } } + +func TestSubjectTreeNode48(t *testing.T) { + var a, b, c leaf[int] + var n node48 + + n.addChild('A', &a) + require_Equal(t, n.key['A'], 1) + require_True(t, n.child[0] != nil) + require_Equal(t, n.child[0].(*leaf[int]), &a) + require_Equal(t, len(n.children()), 1) + + child := n.findChild('A') + require_True(t, child != nil) + require_Equal(t, (*child).(*leaf[int]), &a) + + n.addChild('B', &b) + require_Equal(t, n.key['B'], 2) + require_True(t, n.child[1] != nil) + require_Equal(t, n.child[1].(*leaf[int]), &b) + require_Equal(t, len(n.children()), 2) + + child = n.findChild('B') + require_True(t, child != nil) + require_Equal(t, (*child).(*leaf[int]), &b) + + n.addChild('C', &c) + require_Equal(t, n.key['C'], 3) + require_True(t, n.child[2] != nil) + require_Equal(t, n.child[2].(*leaf[int]), &c) + require_Equal(t, len(n.children()), 3) + + child = n.findChild('C') + require_True(t, child != nil) + require_Equal(t, (*child).(*leaf[int]), &c) + + n.deleteChild('A') + require_Equal(t, len(n.children()), 2) + require_Equal(t, n.key['A'], 0) // Now deleted + require_Equal(t, n.key['B'], 2) // Untouched + require_Equal(t, n.key['C'], 1) // Where A was + + child = n.findChild('A') + require_Equal(t, child, nil) + require_True(t, n.child[0] != nil) + require_Equal(t, n.child[0].(*leaf[int]), &c) + + child = n.findChild('B') + require_True(t, child != nil) + require_Equal(t, (*child).(*leaf[int]), &b) + require_True(t, n.child[1] != nil) + require_Equal(t, n.child[1].(*leaf[int]), &b) + + child = n.findChild('C') + require_True(t, child != nil) + require_Equal(t, (*child).(*leaf[int]), &c) + require_True(t, n.child[2] == nil) + + var gotB, gotC bool + var iterations int + n.iter(func(n node) bool { + iterations++ + if gb, ok := n.(*leaf[int]); ok && &b == gb { + gotB = true + } + if gc, ok := n.(*leaf[int]); ok && &c == gc { + gotC = true + } + return true + }) + require_Equal(t, iterations, 2) + require_True(t, gotB) + require_True(t, gotC) +}