Updated rtree library

This commit is contained in:
tidwall 2021-02-04 08:21:08 -07:00
parent 3ed048242e
commit b37e7395a3
5 changed files with 102 additions and 60 deletions

2
go.mod
View File

@ -21,7 +21,7 @@ require (
github.com/tidwall/gjson v1.6.8 github.com/tidwall/gjson v1.6.8
github.com/tidwall/match v1.0.3 github.com/tidwall/match v1.0.3
github.com/tidwall/pretty v1.0.2 github.com/tidwall/pretty v1.0.2
github.com/tidwall/rbang v1.2.2 github.com/tidwall/rbang v1.2.3
github.com/tidwall/redbench v0.1.0 github.com/tidwall/redbench v0.1.0
github.com/tidwall/redcon v1.4.0 github.com/tidwall/redcon v1.4.0
github.com/tidwall/resp v0.1.0 github.com/tidwall/resp v0.1.0

2
go.sum
View File

@ -141,6 +141,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I= github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I=
github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg= github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
github.com/tidwall/rbang v1.2.3 h1:Eg48GtzQEqqwU6kxAna0H0G/m41bm/MQl/EIqU7jfK8=
github.com/tidwall/rbang v1.2.3/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo= github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ= github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA= github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA=

View File

@ -1,4 +1,4 @@
# `rbang` # rbang
[![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang) [![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang)
@ -27,7 +27,7 @@ var tr rbang.RTree
// insert a point // insert a point
tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX") tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX")
// insert a box // insert a rect
tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect") tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect")
// search // search
@ -48,11 +48,11 @@ This implementation is a variant of the original paper:
### Inserting ### Inserting
Same as the original algorithm. From the root to the leaf, the boxes which will incur the least enlargment are chosen. Ties go to boxes with the smallest area. Same as the original algorithm. From the root to the leaf, the rects which will incur the least enlargment are chosen. Ties go to rects with the smallest area.
### Deleting ### Deleting
Same as the original algorithm. A target box is deleted directly. When the number of children in a box falls below it's minumum entries, it is removed from the tree and it's items are re-inserted. Same as the original algorithm. A target rect is deleted directly. When the number of children in a rect falls below it's minumum entries, it is removed from the tree and it's items are re-inserted.
### Splitting ### Splitting
@ -60,29 +60,19 @@ This is a custom algorithm.
It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes. It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes.
The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory. The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory.
When a box has reached it's max number of entries it's largest axis is calculated and the box is split into two smaller boxes, named `left` and `right`. When a rect has reached it's max number of entries it's largest axis is calculated and the rect is split into two smaller rects, named `left` and `right`.
Each child boxes is then evaluated to determine which smaller box it should be placed into. Each child rects is then evaluated to determine which smaller rect it should be placed into.
Two values, `min-dist` and `max-dist`, are calcuated for each child. Two values, `min-dist` and `max-dist`, are calcuated for each child.
- `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis. - `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis.
- `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis. - `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis.
When the `min-dist` is less than `max-dist` then the child is placed into the `left` box. When the `min-dist` is less than `max-dist` then the child is placed into the `left` rect.
When the `max-dist` is less than `min-dist` then the child is placed into the `right` box. When the `max-dist` is less than `min-dist` then the child is placed into the `right` rect.
When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated. When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated.
Each `equal` box is then one-by-one placed in either `left` or `right`, whichever has less children. Each `equal` rect is then one-by-one placed in either `left` or `right`, whichever has less children.
## Performance
In my testing:
- Insert show similar performance as the quadratic R-tree and ~1.2x - 1.5x faster than R*tree.
- Search and Delete is ~1.5x - 2x faster than quadratic and about the same as R*tree.
I hope to provide more details in the future.
## License ## License
`rbang` source code is available under the MIT License. rbang source code is available under the MIT License.

View File

@ -140,18 +140,49 @@ func (tr *RTree) insert(item *rect) {
tr.count++ tr.count++
} }
func (r *rect) chooseLeastEnlargement(b *rect) int { const inlineEnlargedArea = true
j, jenlargement, jarea := -1, 0.0, 0.0
func (r *rect) chooseLeastEnlargement(b *rect) (index int) {
n := r.data.(*node) n := r.data.(*node)
j, jenlargement, jarea := -1, 0.0, 0.0
for i := 0; i < n.count; i++ { for i := 0; i < n.count; i++ {
area := n.rects[i].area() var earea float64
enlargement := n.rects[i].enlargedArea(b) - area if inlineEnlargedArea {
if j == -1 || enlargement < jenlargement { earea = 1.0
j, jenlargement, jarea = i, enlargement, area if b.max[0] > n.rects[i].max[0] {
} else if enlargement == jenlargement { if b.min[0] < n.rects[i].min[0] {
if area < jarea { earea *= b.max[0] - b.min[0]
j, jenlargement, jarea = i, enlargement, area } else {
earea *= b.max[0] - n.rects[i].min[0]
} }
} else {
if b.min[0] < n.rects[i].min[0] {
earea *= n.rects[i].max[0] - b.min[0]
} else {
earea *= n.rects[i].max[0] - n.rects[i].min[0]
}
}
if b.max[1] > n.rects[i].max[1] {
if b.min[1] < n.rects[i].min[1] {
earea *= b.max[1] - b.min[1]
} else {
earea *= b.max[1] - n.rects[i].min[1]
}
} else {
if b.min[1] < n.rects[i].min[1] {
earea *= n.rects[i].max[1] - b.min[1]
} else {
earea *= n.rects[i].max[1] - n.rects[i].min[1]
}
}
} else {
earea = n.rects[i].enlargedArea(b)
}
area := n.rects[i].area()
enlargement := earea - area
if j == -1 || enlargement < jenlargement ||
(enlargement == jenlargement && area < jarea) {
j, jenlargement, jarea = i, enlargement, area
} }
} }
return j return j
@ -233,8 +264,25 @@ func (r *rect) insert(item *rect, height int) (grown bool) {
grown = !r.contains(item) grown = !r.contains(item)
return grown return grown
} }
// choose subtree // choose subtree
index := r.chooseLeastEnlargement(item) index := -1
narea := 0.0
// first take a quick look for any nodes that contain the rect
for i := 0; i < n.count; i++ {
if n.rects[i].contains(item) {
area := n.rects[i].area()
if index == -1 || area < narea {
narea = area
index = i
}
}
}
// found nothing, now go the slow path
if index == -1 {
index = r.chooseLeastEnlargement(item)
}
// insert the item into the child node
child := &n.rects[index] child := &n.rects[index]
grown = child.insert(item, height-1) grown = child.insert(item, height-1)
if grown { if grown {
@ -374,8 +422,7 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) {
return return
} }
var removed, recalced bool var removed, recalced bool
removed, recalced, tr.reinsert = removed, recalced = tr.root.delete(tr, &item, tr.height)
tr.root.delete(&item, tr.height, tr.reinsert[:0])
if !removed { if !removed {
return return
} }
@ -393,60 +440,62 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) {
if recalced { if recalced {
tr.root.recalc() tr.root.recalc()
} }
if len(tr.reinsert) > 0 {
for i := range tr.reinsert { for i := range tr.reinsert {
tr.insert(&tr.reinsert[i]) tr.insert(&tr.reinsert[i])
tr.reinsert[i].data = nil tr.reinsert[i].data = nil
} }
tr.reinsert = tr.reinsert[:0]
}
} }
func (r *rect) delete(item *rect, height int, reinsert []rect) ( func (r *rect) delete(tr *RTree, item *rect, height int,
removed, recalced bool, reinsertOut []rect, ) (removed, recalced bool) {
) {
n := r.data.(*node) n := r.data.(*node)
rects := n.rects[0:n.count]
if height == 0 { if height == 0 {
for i := 0; i < n.count; i++ { for i := 0; i < len(rects); i++ {
if n.rects[i].data == item.data { if rects[i].data == item.data {
// found the target item to delete // found the target item to delete
recalced = r.onEdge(&n.rects[i]) recalced = r.onEdge(&rects[i])
n.rects[i] = n.rects[n.count-1] rects[i] = rects[len(rects)-1]
n.rects[n.count-1].data = nil rects[len(rects)-1].data = nil
n.count-- n.count--
if recalced { if recalced {
r.recalc() r.recalc()
} }
return true, recalced, reinsert return true, recalced
} }
} }
} else { } else {
for i := 0; i < n.count; i++ { for i := 0; i < len(rects); i++ {
if !n.rects[i].contains(item) { if !rects[i].contains(item) {
continue continue
} }
removed, recalced, reinsert = removed, recalced = rects[i].delete(tr, item, height-1)
n.rects[i].delete(item, height-1, reinsert)
if !removed { if !removed {
continue continue
} }
if n.rects[i].data.(*node).count < minEntries { if rects[i].data.(*node).count < minEntries {
// underflow // underflow
if !recalced { if !recalced {
recalced = r.onEdge(&n.rects[i]) recalced = r.onEdge(&rects[i])
} }
reinsert = n.rects[i].flatten(reinsert, height-1) tr.reinsert = rects[i].flatten(tr.reinsert, height-1)
n.rects[i] = n.rects[n.count-1] rects[i] = rects[len(rects)-1]
n.rects[n.count-1].data = nil rects[len(rects)-1].data = nil
n.count-- n.count--
} }
if recalced { if recalced {
r.recalc() r.recalc()
} }
return removed, recalced, reinsert return removed, recalced
} }
} }
return false, false, reinsert return false, false
} }
// flatten flattens all leaf rects into a single list // flatten all leaf rects into a single list
func (r *rect) flatten(all []rect, height int) []rect { func (r *rect) flatten(all []rect, height int) []rect {
n := r.data.(*node) n := r.data.(*node)
if height == 0 { if height == 0 {
@ -525,8 +574,9 @@ func (tr *RTree) Children(
return children return children
} }
// Replace an item in the structure. This is effectively just a Delete // Replace an item.
// followed by an Insert. // This is effectively just a Delete followed by an Insert. Which means the
// new item will always be inserted, whether or not the old item was deleted.
func (tr *RTree) Replace( func (tr *RTree) Replace(
oldMin, oldMax [2]float64, oldData interface{}, oldMin, oldMax [2]float64, oldData interface{},
newMin, newMax [2]float64, newData interface{}, newMin, newMax [2]float64, newData interface{},

2
vendor/modules.txt vendored
View File

@ -146,7 +146,7 @@ github.com/tidwall/match
# github.com/tidwall/pretty v1.0.2 # github.com/tidwall/pretty v1.0.2
## explicit ## explicit
github.com/tidwall/pretty github.com/tidwall/pretty
# github.com/tidwall/rbang v1.2.2 # github.com/tidwall/rbang v1.2.3
## explicit ## explicit
github.com/tidwall/rbang github.com/tidwall/rbang
# github.com/tidwall/redbench v0.1.0 # github.com/tidwall/redbench v0.1.0