diff --git a/node.go b/node.go index 78d5a92..787ee9c 100644 --- a/node.go +++ b/node.go @@ -120,36 +120,38 @@ func (n *node[V]) nodeAndLeafCountRec() (int, int) { // that collides with a leaf, the compressed leaf is then reinserted // one depth down in the node trie. func (n *node[V]) insertAtDepth(pfx netip.Prefix, val V, depth int) (exists bool) { - octets := pfx.Addr().AsSlice() bits := pfx.Bits() // 10.0.0.0/8 -> 0 // 10.12.0.0/15 -> 1 // 10.12.0.0/16 -> 1 // 10.12.10.9/32 -> 3 - sigOctetIdx := (bits - 1) / strideLen + significantIdx := (bits - 1) / strideLen + + // 10.0.0.0/8 -> 8 + // 10.12.0.0/15 -> 7 + // 10.12.0.0/16 -> 8 + // 10.12.10.9/32 -> 8 + significantBits := bits - (significantIdx * strideLen) // 10.0.0.0/8 -> 10 // 10.12.0.0/15 -> 12 // 10.12.0.0/16 -> 12 // 10.12.10.9/32 -> 9 - // sigOctet := octets[sigOctetIdx] + // significantOctet := octets[significantIdx] - // 10.0.0.0/8 -> 8 - // 10.12.0.0/15 -> 7 - // 10.12.0.0/16 -> 8 - // 10.12.10.9/32 -> 8 - sigOctetBits := bits - (sigOctetIdx * strideLen) + octets := pfx.Addr().AsSlice() + octets = octets[:significantIdx+1] // find the proper trie node to insert prefix // start with prefix octet at depth - for ; depth <= sigOctetIdx; depth++ { + for ; depth < len(octets); depth++ { octet := octets[depth] addr := uint(octet) // last significant octet: insert/override prefix/val into node - if depth == sigOctetIdx { - return n.prefixes.InsertAt(pfxToIdx(octet, sigOctetBits), val) + if depth == significantIdx { + return n.prefixes.InsertAt(pfxToIdx(octet, significantBits), val) } if !n.children.Test(addr) { diff --git a/node_test.go b/node_test.go index 21cee18..d5fb091 100644 --- a/node_test.go +++ b/node_test.go @@ -148,7 +148,7 @@ func TestOverlapsNode(t *testing.T) { } gotGold := gold.strideOverlaps(&goldInter) - gotFast := fast.overlapsRec(fastInter, 0) + gotFast := fast.overlaps(fastInter, 0) if gotGold != gotFast { t.Fatalf("node.overlaps = %v, want %v", gotFast, gotGold) } diff --git a/overlaps.go b/overlaps.go index 067e3a2..8865d32 100644 --- a/overlaps.go +++ b/overlaps.go @@ -6,8 +6,8 @@ import ( "github.com/gaissmai/bart/internal/bitset" ) -// overlapsRec returns true if any IP in the nodes n or o overlaps. -func (n *node[V]) overlapsRec(o *node[V], depth int) bool { +// overlaps returns true if any IP in the nodes n or o overlaps. +func (n *node[V]) overlaps(o *node[V], depth int) bool { nPfxCount := n.prefixes.Len() oPfxCount := o.prefixes.Len() @@ -69,7 +69,7 @@ func (n *node[V]) overlapsRec(o *node[V], depth int) bool { return false } - return n.overlapsSameChildrenRec(o, depth) + return n.overlapsSameChildren(o, depth) } // overlapsRoutes, test if n overlaps o prefixes and vice versa @@ -170,8 +170,8 @@ func (n *node[V]) overlapsChildrenIn(o *node[V]) bool { return prefixRoutes.IntersectionCardinality(hostRoutes) > 0 } -// overlapsSameChildrenRec, find same octets with bitset intersection. -func (n *node[V]) overlapsSameChildrenRec(o *node[V], depth int) bool { +// overlapsSameChildren, find same octets with bitset intersection. +func (n *node[V]) overlapsSameChildren(o *node[V], depth int) bool { var nChildrenBitsetCloned bitset.BitSet = make([]uint64, 4) copy(nChildrenBitsetCloned, n.children.BitSet) @@ -207,7 +207,7 @@ func overlapsTwoChilds[V any](nChild, oChild any, depth int) bool { case *node[V]: switch oKind := oChild.(type) { case *node[V]: // node, node - return nKind.overlapsRec(oKind, depth+1) // node, node + return nKind.overlaps(oKind, depth+1) // node, node case *leaf[V]: // node, leaf return nKind.overlapsPrefixAtDepth(oKind.prefix, depth) // node, node } @@ -236,8 +236,9 @@ func (n *node[V]) overlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool { sigOctetBits := bits - (sigOctetIdx * strideLen) octets := ip.AsSlice() + octets = octets[:sigOctetIdx+1] - for ; depth <= sigOctetIdx; depth++ { + for ; depth < len(octets); depth++ { octet := octets[depth] addr := uint(octet) diff --git a/table.go b/table.go index 5a3b091..cdb6a63 100644 --- a/table.go +++ b/table.go @@ -89,14 +89,14 @@ func (t *Table[V]) Update(pfx netip.Prefix, cb func(val V, ok bool) V) (newVal V n := t.rootNodeByVersion(is4) octets := ip.AsSlice() - sigOctetIdx := (bits - 1) / strideLen - sigOctetBits := bits - (sigOctetIdx * strideLen) + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) // find the proper trie node to update prefix for depth, octet := range octets { // last octet from prefix, update/insert prefix into node - if depth == sigOctetIdx { - newVal, exists := n.prefixes.UpdateAt(pfxToIdx(octet, sigOctetBits), cb) + if depth == significantIdx { + newVal, exists := n.prefixes.UpdateAt(pfxToIdx(octet, significantBits), cb) if !exists { t.sizeUpdate(is4, 1) } @@ -170,8 +170,8 @@ func (t *Table[V]) getAndDelete(pfx netip.Prefix) (val V, ok bool) { n := t.rootNodeByVersion(is4) octets := ip.AsSlice() - sigOctetIdx := (bits - 1) / strideLen - sigOctetBits := bits - (sigOctetIdx * strideLen) + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) // record path to deleted node // needed to purge and/or path compress nodes after deletion @@ -184,8 +184,8 @@ LOOP: stack[depth] = n // try to delete prefix in trie node - if depth == sigOctetIdx { - if val, ok = n.prefixes.DeleteAt(pfxToIdx(octet, sigOctetBits)); ok { + if depth == significantIdx { + if val, ok = n.prefixes.DeleteAt(pfxToIdx(octet, significantBits)); ok { t.sizeUpdate(is4, -1) n.purgeAndCompress(stack[:depth], octets, is4) return val, ok @@ -242,14 +242,14 @@ func (t *Table[V]) Get(pfx netip.Prefix) (val V, ok bool) { n := t.rootNodeByVersion(is4) octets := ip.AsSlice() - sigOctetIdx := (bits - 1) / strideLen - sigOctetBits := bits - (sigOctetIdx * strideLen) + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) // find the trie node LOOP: for depth, octet := range octets { - if depth == sigOctetIdx { - return n.prefixes.Get(pfxToIdx(octet, sigOctetBits)) + if depth == significantIdx { + return n.prefixes.Get(pfxToIdx(octet, significantBits)) } addr := uint(octet) @@ -382,8 +382,7 @@ LOOP: // longest prefix match, skip if node has no prefixes if n.prefixes.Len() != 0 { - octet = octets[depth] - if _, val, ok = n.lpm(hostIndex(uint(octet))); ok { + if _, val, ok = n.lpm(hostIndex(uint(octets[depth]))); ok { return val, ok } } @@ -433,31 +432,30 @@ func (t *Table[V]) lpmPrefix(pfx netip.Prefix) (lpm netip.Prefix, val V, ok bool n := t.rootNodeByVersion(is4) + // see comment in insertAtDepth() + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) + + // do not allocate a16 := ip.As16() octets := a16[:] if is4 { octets = octets[12:] } + octets = octets[:significantIdx+1] - // see comment in Insert() - sigOctetIdx := (bits - 1) / strideLen - sigOctet := octets[sigOctetIdx] - sigOctetBits := bits - (sigOctetIdx * strideLen) + // mask the prefix, pfx.Masked() is too complex and allocates + octets[significantIdx] &= netMask(significantBits) - // mask the prefix - sigOctet &= netMask(sigOctetBits) - octets[sigOctetIdx] = sigOctet - octets = octets[:sigOctetIdx+1] + // record path to leaf node + stack := [maxTreeDepth]*node[V]{} var depth int var octet byte var addr uint - // record path to leaf node - stack := [maxTreeDepth]*node[V]{} - LOOP: - // find the node + // find the last node on the octets path in the trie for depth, octet = range octets { addr = uint(octet) @@ -485,7 +483,7 @@ LOOP: } } - // start backtracking, unwind the stack + // start backtracking, unwind the stack. for ; depth >= 0; depth-- { n = stack[depth] @@ -496,8 +494,8 @@ LOOP: // only the lastOctet may have a different prefix len // all others are just host routes var idx uint - if depth == sigOctetIdx { - idx = pfxToIdx(octet, sigOctetBits) + if depth == significantIdx { + idx = pfxToIdx(octet, significantBits) } else { idx = hostIndex(uint(octet)) } @@ -534,11 +532,11 @@ func (t *Table[V]) Supernets(pfx netip.Prefix) func(yield func(netip.Prefix, V) n := t.rootNodeByVersion(is4) - sigOctetIdx := (bits - 1) / strideLen - sigOctetBits := bits - (sigOctetIdx * strideLen) + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) octets := ip.AsSlice() - octets = octets[:sigOctetIdx+1] + octets = octets[:significantIdx+1] // stack of the traversed nodes for reverse ordering of supernets stack := [maxTreeDepth]*node[V]{} @@ -587,8 +585,8 @@ func (t *Table[V]) Supernets(pfx netip.Prefix) func(yield func(netip.Prefix, V) // only the lastOctet may have a different prefix len // all others are just host routes pfxLen := strideLen - if depth == sigOctetIdx { - pfxLen = sigOctetBits + if depth == significantIdx { + pfxLen = significantBits } if !n.eachLookupPrefix(octets, depth, is4, pfxLen, yield) { @@ -617,16 +615,16 @@ func (t *Table[V]) Subnets(pfx netip.Prefix) func(yield func(netip.Prefix, V) bo n := t.rootNodeByVersion(is4) - sigOctetIdx := (bits - 1) / strideLen - sigOctetBits := bits - (sigOctetIdx * strideLen) + significantIdx := (bits - 1) / strideLen + significantBits := bits - (significantIdx * strideLen) octets := ip.AsSlice() - octets = octets[:sigOctetIdx+1] + octets = octets[:significantIdx+1] // find the trie node for depth, octet := range octets { - if depth == sigOctetIdx { - _ = n.eachSubnet(octets, depth, is4, sigOctetBits, yield) + if depth == significantIdx { + _ = n.eachSubnet(octets, depth, is4, significantBits, yield) return } @@ -677,7 +675,7 @@ func (t *Table[V]) Overlaps4(o *Table[V]) bool { if t.size4 == 0 || o.size4 == 0 { return false } - return t.root4.overlapsRec(&o.root4, 0) + return t.root4.overlaps(&o.root4, 0) } // Overlaps6 reports whether any IPv6 in the table matches a route in the @@ -686,7 +684,7 @@ func (t *Table[V]) Overlaps6(o *Table[V]) bool { if t.size6 == 0 || o.size6 == 0 { return false } - return t.root6.overlapsRec(&o.root6, 0) + return t.root6.overlaps(&o.root6, 0) } // Union combines two tables, changing the receiver table.