Minor optimization to avoid unneeded field merging

This commit is contained in:
tidwall 2022-09-22 14:22:45 -07:00
parent a452d45a1e
commit a824d58419
3 changed files with 81 additions and 58 deletions

View File

@ -51,8 +51,8 @@ func byExpires(a, b *object.Object) bool {
// Collection represents a collection of geojson objects. // Collection represents a collection of geojson objects.
type Collection struct { type Collection struct {
items *btree.BTreeG[*object.Object] // sorted by id objs btree.Map[string, *object.Object] // sorted by id
spatial *rtree.RTreeGN[float32, *object.Object] // geospatially indexed spatial rtree.RTreeGN[float32, *object.Object] // geospatially indexed
values *btree.BTreeG[*object.Object] // sorted by value+id values *btree.BTreeG[*object.Object] // sorted by value+id
expires *btree.BTreeG[*object.Object] // sorted by ex+id expires *btree.BTreeG[*object.Object] // sorted by ex+id
weight int weight int
@ -66,10 +66,8 @@ var optsNoLock = btree.Options{NoLocks: true}
// New creates an empty collection // New creates an empty collection
func New() *Collection { func New() *Collection {
col := &Collection{ col := &Collection{
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.RTreeGN[float32, *object.Object]{},
} }
return col return col
} }
@ -163,7 +161,43 @@ func rtreeRect(rect geometry.Rect) (min, max [2]float32) {
// 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(obj *object.Object) (prev *object.Object) { func (c *Collection) Set(obj *object.Object) (prev *object.Object) {
prev, _ = c.items.Set(obj) prev, _ = c.objs.Set(obj.ID(), obj)
c.setFill(prev, obj)
return prev
}
// SetMerged works just like Set but it will merge the new object fields and
// the previous object fields and create a newer object that is then set into
// the collection. The newer object is returned.
func (c *Collection) SetMerged(obj *object.Object,
) (prev, newObj *object.Object) {
prev, _ = c.objs.Set(obj.ID(), obj)
if prev != nil {
// Check if at least one field exists from the previous object and
// merge the two field lists, then re-set the new object. Otherwise,
// we stick with the current object.
// TODO: check if the old object has fields that new object does not
// and only reset those.
ofields := prev.Fields()
var reset bool
ofields.Scan(func(f field.Field) bool {
reset = true
return false
})
if reset {
obj.Fields().Scan(func(f field.Field) bool {
ofields = ofields.Set(f)
return true
})
obj = object.New(obj.ID(), obj.Geo(), obj.Expires(), ofields)
c.objs.Set(obj.ID(), obj)
}
}
c.setFill(prev, obj)
return prev, obj
}
func (c *Collection) setFill(prev, obj *object.Object) {
if prev != nil { if prev != nil {
if prev.IsSpatial() { if prev.IsSpatial() {
c.indexDelete(prev) c.indexDelete(prev)
@ -190,14 +224,12 @@ func (c *Collection) Set(obj *object.Object) (prev *object.Object) {
} }
c.points += obj.Geo().NumPoints() c.points += obj.Geo().NumPoints()
c.weight += obj.Weight() c.weight += obj.Weight()
return prev
} }
// Delete removes an object and returns it. // Delete removes an object and returns it.
// If the object does not exist then the 'ok' return value will be false. // If the object does not exist then the 'ok' return value will be false.
func (c *Collection) Delete(id string) (prev *object.Object) { func (c *Collection) Delete(id string) (prev *object.Object) {
key := object.New(id, nil, 0, field.List{}) prev, _ = c.objs.Delete(id)
prev, _ = c.items.Delete(key)
if prev == nil { if prev == nil {
return nil return nil
} }
@ -221,8 +253,7 @@ func (c *Collection) Delete(id string) (prev *object.Object) {
// Get returns an object. // Get returns an object.
// If the object does not exist then the 'ok' return value will be false. // If the object does not exist then the 'ok' return value will be false.
func (c *Collection) Get(id string) *object.Object { func (c *Collection) Get(id string) *object.Object {
key := object.New(id, nil, 0, field.List{}) obj, _ := c.objs.Get(id)
obj, _ := c.items.Get(key)
return obj return obj
} }
@ -240,7 +271,7 @@ func (c *Collection) Scan(
offset = cursor.Offset() offset = cursor.Offset()
cursor.Step(offset) cursor.Step(offset)
} }
iter := func(obj *object.Object) bool { iter := func(_ string, obj *object.Object) bool {
count++ count++
if count <= offset { if count <= offset {
return true return true
@ -250,9 +281,9 @@ func (c *Collection) Scan(
return keepon return keepon
} }
if desc { if desc {
c.items.Reverse(iter) c.objs.Reverse(iter)
} else { } else {
c.items.Scan(iter) c.objs.Scan(iter)
} }
return keepon return keepon
} }
@ -272,7 +303,7 @@ func (c *Collection) ScanRange(
offset = cursor.Offset() offset = cursor.Offset()
cursor.Step(offset) cursor.Step(offset)
} }
iter := func(o *object.Object) bool { iter := func(_ string, o *object.Object) bool {
count++ count++
if count <= offset { if count <= offset {
return true return true
@ -291,11 +322,10 @@ func (c *Collection) ScanRange(
return keepon return keepon
} }
pstart := object.New(start, nil, 0, field.List{})
if desc { if desc {
c.items.Descend(pstart, iter) c.objs.Descend(start, iter)
} else { } else {
c.items.Ascend(pstart, iter) c.objs.Ascend(start, iter)
} }
return keepon return keepon
} }
@ -385,7 +415,7 @@ func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
offset = cursor.Offset() offset = cursor.Offset()
cursor.Step(offset) cursor.Step(offset)
} }
iter := func(o *object.Object) bool { iter := func(_ string, o *object.Object) bool {
count++ count++
if count <= offset { if count <= offset {
return true return true
@ -394,11 +424,10 @@ func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
keepon = iterator(o) keepon = iterator(o)
return keepon return keepon
} }
pstart := object.New(id, nil, 0, field.List{})
if desc { if desc {
c.items.Descend(pstart, iter) c.objs.Descend(id, iter)
} else { } else {
c.items.Ascend(pstart, iter) c.objs.Ascend(id, iter)
} }
return keepon return keepon
} }

View File

@ -351,11 +351,12 @@ func (fields List) Weight() int {
return x + n return x + n
} }
// Bytes returns the raw bytes (including the header) // MakeList returns a field list from an array of fields.
func (fields List) Bytes() []byte { func MakeList(fields []Field) List {
if fields.p == nil { // TODO: optimize to reduce allocations.
return nil var list List
for _, f := range fields {
list = list.Set(f)
} }
x, n := uvarint(*(*[]byte)(unsafe.Pointer(&bytes{fields.p, 10, 10}))) return list
return (*(*[]byte)(unsafe.Pointer(&bytes{fields.p, 0, n + x})))
} }

View File

@ -677,31 +677,7 @@ func (s *Server) cmdSET(msg *Message) (resp.Value, commandDetails, error) {
// >> Operation // >> Operation
var nada bool nada := func() (resp.Value, commandDetails, error) {
col, ok := s.cols.Get(key)
if !ok {
if xx {
nada = true
} else {
col = collection.New()
s.cols.Set(key, col)
}
}
var ofields field.List
if !nada {
o := col.Get(id)
if o != nil {
ofields = o.Fields()
}
if xx || nx {
if (nx && ok) || (xx && !ok) {
nada = true
}
}
}
if nada {
// exclude operation due to 'xx' or 'nx' match // exclude operation due to 'xx' or 'nx' match
switch msg.OutputType { switch msg.OutputType {
default: default:
@ -717,12 +693,29 @@ func (s *Server) cmdSET(msg *Message) (resp.Value, commandDetails, error) {
return retwerr(errors.New("nada unknown output")) return retwerr(errors.New("nada unknown output"))
} }
for _, f := range fields { col, ok := s.cols.Get(key)
ofields = ofields.Set(f) if !ok {
if xx {
return nada()
}
col = collection.New()
s.cols.Set(key, col)
} }
obj := object.New(id, oobj, ex, ofields) if xx || nx {
old := col.Set(obj) if col.Get(id) == nil {
if xx {
return nada()
}
} else {
if nx {
return nada()
}
}
}
obj := object.New(id, oobj, ex, field.MakeList(fields))
old, obj := col.SetMerged(obj)
// >> Response // >> Response