Changed the collection rectangle dimension type

Previously used float64s, now using float32s.
Saving about 15% on rectangle memory.
Uses the Roundoff trick from Sqlite.
This commit is contained in:
tidwall 2022-09-19 17:51:14 -07:00
parent d5c148ca41
commit ba9a767988
4 changed files with 92 additions and 32 deletions

2
go.mod
View File

@ -27,7 +27,7 @@ require (
github.com/tidwall/redbench v0.1.0
github.com/tidwall/redcon v1.4.4
github.com/tidwall/resp v0.1.1
github.com/tidwall/rtree v1.8.1
github.com/tidwall/rtree v1.9.1
github.com/tidwall/sjson v1.2.4
github.com/xdg/scram v1.0.5
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da

4
go.sum
View File

@ -383,8 +383,8 @@ github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYg
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/rtree v1.3.1/go.mod h1:S+JSsqPTI8LfWA4xHBo5eXzie8WJLVFeppAutSegl6M=
github.com/tidwall/rtree v1.8.1 h1:Hv0gvvznkDI5YwBkJp9pYh8ZEU1L2A9puLwwGPcQ9j4=
github.com/tidwall/rtree v1.8.1/go.mod h1:iDJQ9NBRtbfKkzZu02za+mIlaP+bjYPnunbSNidpbCQ=
github.com/tidwall/rtree v1.9.1 h1:UIPtvE09nLKZRnMNEwRZxu9jRAkzROAZDR+NPS/9IRs=
github.com/tidwall/rtree v1.9.1/go.mod h1:iDJQ9NBRtbfKkzZu02za+mIlaP+bjYPnunbSNidpbCQ=
github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=

View File

@ -56,10 +56,17 @@ func byExpires(a, b *itemT) bool {
return byID(a, b)
}
func (item *itemT) Rect() geometry.Rect {
if item.obj != nil {
return item.obj.Rect()
}
return geometry.Rect{}
}
// Collection represents a collection of geojson objects.
type Collection struct {
items *btree.BTreeG[*itemT] // items sorted by id
spatial *rtree.RTreeG[*itemT] // items geospatially indexed
spatial *rtree.RTreeGN[float32, *itemT] // items geospatially indexed
values *btree.BTreeG[*itemT] // items sorted by value+id
expires *btree.BTreeG[*itemT] // items sorted by ex+id
weight int
@ -76,7 +83,7 @@ func New() *Collection {
items: btree.NewBTreeGOptions(byID, optsNoLock),
values: btree.NewBTreeGOptions(byValue, optsNoLock),
expires: btree.NewBTreeGOptions(byExpires, optsNoLock),
spatial: &rtree.RTreeG[*itemT]{},
spatial: &rtree.RTreeGN[float32, *itemT]{},
}
return col
}
@ -103,11 +110,15 @@ func (c *Collection) TotalWeight() int {
// Bounds returns the bounds of all the items in the collection.
func (c *Collection) Bounds() (minX, minY, maxX, maxY float64) {
min, max := c.spatial.Bounds()
if len(min) >= 2 && len(max) >= 2 {
return min[0], min[1], max[0], max[1]
}
_, _, left := c.spatial.LeftMost()
_, _, bottom := c.spatial.BottomMost()
_, _, right := c.spatial.RightMost()
_, _, top := c.spatial.TopMost()
if left == nil {
return
}
return left.Rect().Min.X, bottom.Rect().Min.Y,
right.Rect().Max.X, top.Rect().Max.Y
}
func objIsSpatial(obj geojson.Object) bool {
@ -129,21 +140,54 @@ func (c *Collection) objWeight(item *itemT) int {
func (c *Collection) indexDelete(item *itemT) {
if !item.obj.Empty() {
rect := item.obj.Rect()
c.spatial.Delete(
[2]float64{rect.Min.X, rect.Min.Y},
[2]float64{rect.Max.X, rect.Max.Y},
item)
c.spatial.Delete(rtreeItem(item))
}
}
func (c *Collection) indexInsert(item *itemT) {
if !item.obj.Empty() {
rect := item.obj.Rect()
c.spatial.Insert(
[2]float64{rect.Min.X, rect.Min.Y},
[2]float64{rect.Max.X, rect.Max.Y},
item)
c.spatial.Insert(rtreeItem(item))
}
}
const dRNDTOWARDS = (1.0 - 1.0/8388608.0) /* Round towards zero */
const dRNDAWAY = (1.0 + 1.0/8388608.0) /* Round away from zero */
func rtreeValueDown(d float64) float32 {
f := float32(d)
if float64(f) > d {
if d < 0 {
f = float32(d * dRNDAWAY)
} else {
f = float32(d * dRNDTOWARDS)
}
}
return f
}
func rtreeValueUp(d float64) float32 {
f := float32(d)
if float64(f) < d {
if d < 0 {
f = float32(d * dRNDTOWARDS)
} else {
f = float32(d * dRNDAWAY)
}
}
return f
}
func rtreeItem(item *itemT) (min, max [2]float32, data *itemT) {
min, max = rtreeRect(item.Rect())
return min, max, item
}
func rtreeRect(rect geometry.Rect) (min, max [2]float32) {
return [2]float32{
rtreeValueDown(rect.Min.X),
rtreeValueDown(rect.Min.Y),
}, [2]float32{
rtreeValueUp(rect.Max.X),
rtreeValueUp(rect.Max.Y),
}
}
@ -429,10 +473,10 @@ func (c *Collection) geoSearch(
iter func(id string, obj geojson.Object, fields field.List) bool,
) bool {
alive := true
min, max := rtreeRect(rect)
c.spatial.Search(
[2]float64{rect.Min.X, rect.Min.Y},
[2]float64{rect.Max.X, rect.Max.Y},
func(_, _ [2]float64, item *itemT) bool {
min, max,
func(_, _ [2]float32, item *itemT) bool {
alive = iter(item.id, item.obj, item.fields)
return alive
},
@ -618,10 +662,19 @@ func (c *Collection) Nearby(
minLat, minLon, maxLat, maxLon :=
geo.RectFromCenter(center.Y, center.X, meters)
var exists bool
min, max := rtreeRect(geometry.Rect{
Min: geometry.Point{
X: minLon,
Y: minLat,
},
Max: geometry.Point{
X: maxLon,
Y: maxLat,
},
})
c.spatial.Search(
[2]float64{minLon, minLat},
[2]float64{maxLon, maxLat},
func(_, _ [2]float64, item *itemT) bool {
min, max,
func(_, _ [2]float32, item *itemT) bool {
exists = true
return false
},
@ -641,15 +694,22 @@ func (c *Collection) Nearby(
offset = cursor.Offset()
cursor.Step(offset)
}
distFn := geodeticDistAlgo[*itemT]([2]float64{center.X, center.Y})
c.spatial.Nearby(
geodeticDistAlgo[*itemT]([2]float64{center.X, center.Y}),
func(_, _ [2]float64, item *itemT, dist float64) bool {
func(min, max [2]float32, data *itemT, item bool) float32 {
return float32(distFn(
[2]float64{float64(min[0]), float64(min[1])},
[2]float64{float64(max[0]), float64(max[1])},
data, item,
))
},
func(_, _ [2]float32, item *itemT, dist float32) bool {
count++
if count <= offset {
return true
}
nextStep(count, cursor, deadline)
alive = iter(item.id, item.obj, item.fields, dist)
alive = iter(item.id, item.obj, item.fields, float64(dist))
return alive
},
)

View File

@ -46,7 +46,7 @@ func keys_KNN_basic_test(mc *mockServer) error {
{"NEARBY", "mykey", "LIMIT", 10, "POINTS", "POINT", 20, 20}, {
"[0 [[2 [19 19]] [3 [12 19]] [5 [33 21]] [1 [5 5]] [4 [-5 5]] [6 [52 13]]]]"},
{"NEARBY", "mykey", "LIMIT", 10, "IDS", "POINT", 20, 20, 4000000}, {"[0 [2 3 5 1 4 6]]"},
{"NEARBY", "mykey", "LIMIT", 10, "DISTANCE", "IDS", "POINT", 20, 20, 1500000}, {"[0 [[2 152808.67164037024] [3 895945.1409106688] [5 1448929.5916252395]]]"},
{"NEARBY", "mykey", "LIMIT", 10, "DISTANCE", "IDS", "POINT", 20, 20, 1500000}, {"[0 [[2 152808.671875] [3 895945.125] [5 1448929.625]]]"},
{"NEARBY", "mykey", "LIMIT", 10, "DISTANCE", "POINT", 52, 13, 100}, {`[0 [[6 {"type":"Point","coordinates":[13,52]} 0]]]`},
{"NEARBY", "mykey", "LIMIT", 10, "POINT", 52.1, 13.1, 100000}, {`[0 [[6 {"type":"Point","coordinates":[13,52]}]]]`},
{"OUTPUT", "json"}, {func(res string) bool { return gjson.Get(res, "ok").Bool() }},