Evgenii Stratonikov
15102e6dfd
`slices.SortFunc` doesn't use reflection and is a bit faster. I have done some micro-benchmarks for `[]NodeInfo`: ``` $ benchstat -col "/func" out goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ sort.Slice │ slices.SortFunc │ │ sec/op │ sec/op vs base │ Sort-8 2.130µ ± 2% 1.253µ ± 2% -41.20% (p=0.000 n=10) ``` Haven't included them, though, as they I don't see them being used a lot. Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
213 lines
4.3 KiB
Go
213 lines
4.3 KiB
Go
package pilorama
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
)
|
|
|
|
// nodeInfo couples parent and metadata.
|
|
type nodeInfo struct {
|
|
Parent Node
|
|
Meta Meta
|
|
}
|
|
|
|
type move struct {
|
|
Move
|
|
HasOld bool
|
|
Old nodeInfo
|
|
}
|
|
|
|
// memoryTree represents memoryTree being replicated.
|
|
type memoryTree struct {
|
|
operations []move
|
|
tree
|
|
}
|
|
|
|
// newMemoryTree constructs new empty tree.
|
|
func newMemoryTree() *memoryTree {
|
|
return &memoryTree{
|
|
tree: tree{
|
|
infoMap: make(map[Node]nodeInfo),
|
|
},
|
|
}
|
|
}
|
|
|
|
// undo un-does op and changes s in-place.
|
|
func (s *memoryTree) undo(op *move) {
|
|
if op.HasOld {
|
|
s.tree.infoMap[op.Child] = op.Old
|
|
} else {
|
|
delete(s.tree.infoMap, op.Child)
|
|
}
|
|
}
|
|
|
|
// Apply puts op in log at a proper position, re-applies all subsequent operations
|
|
// from log and changes s in-place.
|
|
func (s *memoryTree) Apply(op *Move) error {
|
|
var index int
|
|
for index = len(s.operations); index > 0; index-- {
|
|
if s.operations[index-1].Time <= op.Time {
|
|
break
|
|
}
|
|
}
|
|
|
|
if index == len(s.operations) {
|
|
s.operations = append(s.operations, s.do(op))
|
|
return nil
|
|
}
|
|
|
|
s.operations = append(s.operations[:index+1], s.operations[index:]...)
|
|
for i := len(s.operations) - 1; i > index; i-- {
|
|
s.undo(&s.operations[i])
|
|
}
|
|
|
|
s.operations[index] = s.do(op)
|
|
|
|
for i := index + 1; i < len(s.operations); i++ {
|
|
s.operations[i] = s.do(&s.operations[i].Move)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// do performs a single move operation on a tree.
|
|
func (s *memoryTree) do(op *Move) move {
|
|
m := op.Meta
|
|
if m.Items == nil {
|
|
m.Items = []KeyValue{}
|
|
}
|
|
lm := move{
|
|
Move: Move{
|
|
Parent: op.Parent,
|
|
Meta: m,
|
|
Child: op.Child,
|
|
},
|
|
}
|
|
|
|
shouldPut := !s.tree.isAncestor(op.Child, op.Parent)
|
|
p, ok := s.tree.infoMap[op.Child]
|
|
if ok {
|
|
lm.HasOld = true
|
|
lm.Old = p
|
|
}
|
|
|
|
if !shouldPut {
|
|
return lm
|
|
}
|
|
|
|
if !ok {
|
|
p.Meta.Time = op.Time
|
|
}
|
|
|
|
p.Meta = m
|
|
p.Parent = op.Parent
|
|
s.tree.infoMap[op.Child] = p
|
|
|
|
return lm
|
|
}
|
|
|
|
func (s *memoryTree) timestamp(pos, size int) Timestamp {
|
|
if len(s.operations) == 0 {
|
|
return nextTimestamp(0, uint64(pos), uint64(size))
|
|
}
|
|
return nextTimestamp(s.operations[len(s.operations)-1].Time, uint64(pos), uint64(size))
|
|
}
|
|
|
|
func (s *memoryTree) findSpareID() Node {
|
|
id := uint64(1)
|
|
for _, ok := s.infoMap[id]; ok; _, ok = s.infoMap[id] {
|
|
id++
|
|
}
|
|
return id
|
|
}
|
|
|
|
// tree is a mapping from the child nodes to their parent and metadata.
|
|
type tree struct {
|
|
syncHeight uint64
|
|
infoMap map[Node]nodeInfo
|
|
}
|
|
|
|
func (t tree) getChildren(parent Node) []Node {
|
|
var children []Node
|
|
for c, info := range t.infoMap {
|
|
if info.Parent == parent {
|
|
children = append(children, c)
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(children, func(ci, cj uint64) int {
|
|
a := t.infoMap[ci]
|
|
b := t.infoMap[cj]
|
|
return cmp.Compare(a.Meta.Time, b.Meta.Time)
|
|
})
|
|
return children
|
|
}
|
|
|
|
// isAncestor returns true if parent is an ancestor of a child.
|
|
// For convenience, also return true if parent == child.
|
|
func (t tree) isAncestor(parent, child Node) bool {
|
|
for c := child; c != parent; {
|
|
p, ok := t.infoMap[c]
|
|
if !ok {
|
|
return false
|
|
}
|
|
c = p.Parent
|
|
}
|
|
return true
|
|
}
|
|
|
|
// getPathPrefix descends by path constructed from values of attr until
|
|
// there is no node corresponding to a path element. Returns the amount of nodes
|
|
// processed and ID of the last node.
|
|
func (t tree) getPathPrefix(attr string, path []string) (int, Node) {
|
|
var curNode Node
|
|
|
|
loop:
|
|
for i := range path {
|
|
children := t.getChildren(curNode)
|
|
for j := range children {
|
|
meta := t.infoMap[children[j]].Meta
|
|
f := meta.GetAttr(attr)
|
|
if len(meta.Items) == 1 && string(f) == path[i] {
|
|
curNode = children[j]
|
|
continue loop
|
|
}
|
|
}
|
|
return i, curNode
|
|
}
|
|
|
|
return len(path), curNode
|
|
}
|
|
|
|
// getByPath returns list of nodes which have the specified path from root
|
|
// descending by values of attr from meta.
|
|
// If latest is true, only the latest node is returned.
|
|
func (t tree) getByPath(attr string, path []string, latest bool) []Node {
|
|
if len(path) == 0 {
|
|
return nil
|
|
}
|
|
|
|
i, curNode := t.getPathPrefix(attr, path[:len(path)-1])
|
|
if i < len(path)-1 {
|
|
return nil
|
|
}
|
|
|
|
var nodes []Node
|
|
var lastTs Timestamp
|
|
|
|
children := t.getChildren(curNode)
|
|
for i := range children {
|
|
info := t.infoMap[children[i]]
|
|
fileName := string(info.Meta.GetAttr(attr))
|
|
if fileName == path[len(path)-1] {
|
|
if latest {
|
|
if info.Meta.Time >= lastTs {
|
|
nodes = append(nodes[:0], children[i])
|
|
}
|
|
} else {
|
|
nodes = append(nodes, children[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
return nodes
|
|
}
|