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/redbench v0.1.0
github.com/tidwall/redcon v1.4.4 github.com/tidwall/redcon v1.4.4
github.com/tidwall/resp v0.1.1 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/tidwall/sjson v1.2.4
github.com/xdg/scram v1.0.5 github.com/xdg/scram v1.0.5
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da 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 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= 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.3.1/go.mod h1:S+JSsqPTI8LfWA4xHBo5eXzie8WJLVFeppAutSegl6M=
github.com/tidwall/rtree v1.8.1 h1:Hv0gvvznkDI5YwBkJp9pYh8ZEU1L2A9puLwwGPcQ9j4= github.com/tidwall/rtree v1.9.1 h1:UIPtvE09nLKZRnMNEwRZxu9jRAkzROAZDR+NPS/9IRs=
github.com/tidwall/rtree v1.8.1/go.mod h1:iDJQ9NBRtbfKkzZu02za+mIlaP+bjYPnunbSNidpbCQ= 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 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=

View File

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

View File

@ -46,7 +46,7 @@ func keys_KNN_basic_test(mc *mockServer) error {
{"NEARBY", "mykey", "LIMIT", 10, "POINTS", "POINT", 20, 20}, { {"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]]]]"}, "[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, "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, "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]}]]]`}, {"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() }}, {"OUTPUT", "json"}, {func(res string) bool { return gjson.Get(res, "ok").Bool() }},