Skip to content

Commit

Permalink
Remove the linked list from crdt.Tree
Browse files Browse the repository at this point in the history
  • Loading branch information
hackerwins committed Aug 16, 2023
1 parent 43c41a3 commit d923c51
Showing 1 changed file with 30 additions and 101 deletions.
131 changes: 30 additions & 101 deletions pkg/document/crdt/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ type TreeNode struct {
ID *TreeNodeID
RemovedAt *time.Ticket

Next *TreeNode
Prev *TreeNode
InsPrev *TreeNode
InsNext *TreeNode

Value string
Attrs *RHT
}

// TreePos represents the position of Tree.
// TreePos represents a position in the tree. It is used to determine the
// position of insertion, deletion, and style change.
type TreePos struct {
ParentID *TreeNodeID
// ParentID is the ID of the parent node.
ParentID *TreeNodeID

// LeftSiblingID is the ID of the left sibling node. If the node is the
// parent, it means that the position is leftmost.
LeftSiblingID *TreeNodeID
}

Expand Down Expand Up @@ -99,9 +102,23 @@ func NewTreeNodeID(createdAt *time.Ticket, offset int) *TreeNodeID {
}
}

// ToIDString returns a string that can be used as an ID for this TreeNodeID.
func (t *TreeNodeID) ToIDString() string {
// TODO(sejongk): change this to be private
// NewTreeNode creates a new instance of TreeNode.
func NewTreeNode(id *TreeNodeID, nodeType string, attributes *RHT, value ...string) *TreeNode {
node := &TreeNode{ID: id}

// NOTE(hackerwins): The value of TreeNode is optional. If the value is
// empty, it means that the node is an element node.
if len(value) > 0 {
node.Value = value[0]
}
node.Attrs = attributes
node.IndexTreeNode = index.NewNode(nodeType, node)

return node
}

// toIDString returns a string that can be used as an ID for this TreeNodeID.
func (t *TreeNodeID) toIDString() string {
return t.CreatedAt.StructureAsString() + ":" + strconv.Itoa(t.Offset)
}

Expand All @@ -120,22 +137,6 @@ func (t *TreeNodeID) Compare(other llrb.Key) int {
return 0
}

// NewTreeNode creates a new instance of TreeNode.
func NewTreeNode(pos *TreeNodeID, nodeType string, attributes *RHT, value ...string) *TreeNode {
node := &TreeNode{
ID: pos,
}

if len(value) > 0 {
node.Value = value[0]
}
node.Attrs = attributes

node.IndexTreeNode = index.NewNode(nodeType, node)

return node
}

// Type returns the type of the Node.
func (n *TreeNode) Type() string {
return n.IndexTreeNode.Type
Expand Down Expand Up @@ -176,11 +177,6 @@ func (n *TreeNode) Attributes() string {
return " " + n.Attrs.ToXML()
}

// NextNode returns the next node of this TreeNode.
func (n *TreeNode) NextNode() *TreeNode {
return n.Next
}

// Append appends the given node to the end of the children.
func (n *TreeNode) Append(newNodes ...*TreeNode) error {
indexNodes := make([]*index.Node[*TreeNode], len(newNodes))
Expand Down Expand Up @@ -391,24 +387,15 @@ func (t *Tree) purgeRemovedNodesBefore(ticket *time.Ticket) (int, error) {
}
t.NodeMapByPos.Remove(node.ID)
t.Purge(node)
delete(t.removedNodeMap, node.ID.ToIDString())
delete(t.removedNodeMap, node.ID.toIDString())
}

return count, nil
}

// Purge physically purges the given node.
func (t *Tree) Purge(node *TreeNode) {
if node.Prev != nil {
node.Prev.Next = node.Next
}

if node.Next != nil {
node.Next.Prev = node.Prev
}

node.Prev = nil
node.Next = nil
// TODO(hackerwins): Figure out how to purge the node from the index tree.
node.InsPrev = nil
}

Expand Down Expand Up @@ -481,20 +468,6 @@ func (t *Tree) Remove(removedAt *time.Ticket) bool {
return false
}

// InsertAfter inserts the given node after the given previous node.
func (t *Tree) InsertAfter(prevNode *TreeNode, newNode *TreeNode) {
next := prevNode.Next
prevNode.Next = newNode
newNode.Prev = prevNode

if next != nil {
newNode.Next = next
next.Prev = newNode
}

t.NodeMapByPos.Put(newNode.ID, newNode)
}

// Nodes traverses the tree and returns the list of nodes.
func (t *Tree) Nodes() []*TreeNode {
var nodes []*TreeNode
Expand Down Expand Up @@ -643,7 +616,7 @@ func (t *Tree) Edit(from, to *TreePos,
createdAtMapByActor[actorIDHex] = createdAt
}

t.removedNodeMap[node.ID.ToIDString()] = node
t.removedNodeMap[node.ID.toIDString()] = node

// traverse the nodes including tombstones
index.TraverseNode(node.IndexTreeNode, func(node *index.Node[*TreeNode], depth int) {
Expand All @@ -655,7 +628,7 @@ func (t *Tree) Edit(from, to *TreePos,
createdAtMapByActor[actorIDHex] = createdAt
}

t.removedNodeMap[node.Value.ID.ToIDString()] = node.Value
t.removedNodeMap[node.Value.ID.toIDString()] = node.Value
}
})
}
Expand Down Expand Up @@ -695,7 +668,7 @@ func (t *Tree) Edit(from, to *TreePos,
createdAtMapByActor[actorIDHex] = createdAt
}
}
t.removedNodeMap[node.Value.ID.ToIDString()] = node.Value
t.removedNodeMap[node.Value.ID.toIDString()] = node.Value
}

t.NodeMapByPos.Put(node.Value.ID, node.Value)
Expand Down Expand Up @@ -768,41 +741,13 @@ func (t *Tree) Style(from, to *TreePos, attributes map[string]string, editedAt *
return nil
}

// findTreePos returns TreePos and the right node of the given index in postorder.
func (t *Tree) findTreePos(pos *TreePos, editedAt *time.Ticket) (*index.TreePos[*TreeNode], *TreeNode, error) {
treePos := t.toTreePos(pos)
if treePos == nil {
return nil, nil, fmt.Errorf("%p: %w", pos, ErrNodeNotFound)
}

// Find the appropriate position. This logic is similar to the logical to
// handle the same position insertion of RGA.
current := treePos
for current.Node.Value.Next != nil && current.Node.Value.Next.ID.CreatedAt.After(editedAt) &&
current.Node.Value.IndexTreeNode.Parent == current.Node.Value.Next.IndexTreeNode.Parent {

current = &index.TreePos[*TreeNode]{
Node: current.Node.Value.Next.IndexTreeNode,
Offset: current.Node.Value.Next.Len(),
}
}

// TODO(hackerwins): Consider to use current instead of treePos.
right, err := t.IndexTree.FindPostorderRight(treePos)
if err != nil {
return nil, nil, err
}

return current, right, nil
}

/**
* TODO(sejongk): clarify the comments
* findTreeNodesWithSplitText finds TreeNode of the given crdt.TreePos and
* splits the text node if necessary.
*
* crdt.TreePos is a position in the CRDT perspective. This is different
* from indexTree.TreePos which is a position of the tree in the local perspective.
* TODO(sejongk): clarify the comments
**/
func (t *Tree) findTreeNodesWithSplitText(pos *TreePos, editedAt *time.Ticket) (
*index.Node[*TreeNode], *index.Node[*TreeNode], error,
Expand Down Expand Up @@ -927,22 +872,6 @@ func (t *Tree) toTreeNodes(pos *TreePos) (*TreeNode, *TreeNode) {
return parentNode, leftSiblingNode
}

// nodesBetween returns the nodes between the given range.
// This method includes the given left node but excludes the given right node.
func (t *Tree) nodesBetween(left *TreeNode, right *TreeNode, callback func(*TreeNode)) error {
current := left
for current != right {
if current == nil {
return errors.New("left and right are not in the same list")
}

callback(current)
current = current.Next
}

return nil
}

// Structure returns the structure of this tree.
func (t *Tree) Structure() TreeNodeForTest {
return ToStructure(t.Root())
Expand Down

1 comment on commit d923c51

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go Benchmark

Benchmark suite Current: d923c51 Previous: 1f63722 Ratio
BenchmarkDocument/constructor_test - ns/op 1801 ns/op 1571 ns/op 1.15
BenchmarkDocument/constructor_test - B/op 984 B/op 984 B/op 1
BenchmarkDocument/constructor_test - allocs/op 16 allocs/op 16 allocs/op 1
BenchmarkDocument/status_test - ns/op 1086 ns/op 889.9 ns/op 1.22
BenchmarkDocument/status_test - B/op 952 B/op 952 B/op 1
BenchmarkDocument/status_test - allocs/op 14 allocs/op 14 allocs/op 1
BenchmarkDocument/equals_test - ns/op 10078 ns/op 8561 ns/op 1.18
BenchmarkDocument/equals_test - B/op 6193 B/op 6192 B/op 1.00
BenchmarkDocument/equals_test - allocs/op 106 allocs/op 106 allocs/op 1
BenchmarkDocument/nested_update_test - ns/op 25656 ns/op 23849 ns/op 1.08
BenchmarkDocument/nested_update_test - B/op 11689 B/op 11689 B/op 1
BenchmarkDocument/nested_update_test - allocs/op 248 allocs/op 248 allocs/op 1
BenchmarkDocument/delete_test - ns/op 41118 ns/op 30251 ns/op 1.36
BenchmarkDocument/delete_test - B/op 14914 B/op 14915 B/op 1.00
BenchmarkDocument/delete_test - allocs/op 327 allocs/op 327 allocs/op 1
BenchmarkDocument/object_test - ns/op 12360 ns/op 10605 ns/op 1.17
BenchmarkDocument/object_test - B/op 6449 B/op 6448 B/op 1.00
BenchmarkDocument/object_test - allocs/op 110 allocs/op 110 allocs/op 1
BenchmarkDocument/array_test - ns/op 42409 ns/op 40110 ns/op 1.06
BenchmarkDocument/array_test - B/op 11545 B/op 11545 B/op 1
BenchmarkDocument/array_test - allocs/op 264 allocs/op 264 allocs/op 1
BenchmarkDocument/text_test - ns/op 50150 ns/op 38207 ns/op 1.31
BenchmarkDocument/text_test - B/op 14618 B/op 14618 B/op 1
BenchmarkDocument/text_test - allocs/op 470 allocs/op 470 allocs/op 1
BenchmarkDocument/text_composition_test - ns/op 48116 ns/op 38782 ns/op 1.24
BenchmarkDocument/text_composition_test - B/op 18002 B/op 18002 B/op 1
BenchmarkDocument/text_composition_test - allocs/op 471 allocs/op 471 allocs/op 1
BenchmarkDocument/rich_text_test - ns/op 123647 ns/op 103540 ns/op 1.19
BenchmarkDocument/rich_text_test - B/op 36807 B/op 36807 B/op 1
BenchmarkDocument/rich_text_test - allocs/op 1131 allocs/op 1131 allocs/op 1
BenchmarkDocument/counter_test - ns/op 24896 ns/op 20994 ns/op 1.19
BenchmarkDocument/counter_test - B/op 9971 B/op 9970 B/op 1.00
BenchmarkDocument/counter_test - allocs/op 235 allocs/op 235 allocs/op 1
BenchmarkDocument/text_edit_gc_100 - ns/op 4642639 ns/op 3963886 ns/op 1.17
BenchmarkDocument/text_edit_gc_100 - B/op 1553079 B/op 1553087 B/op 1.00
BenchmarkDocument/text_edit_gc_100 - allocs/op 17161 allocs/op 17161 allocs/op 1
BenchmarkDocument/text_edit_gc_1000 - ns/op 361746517 ns/op 313689090 ns/op 1.15
BenchmarkDocument/text_edit_gc_1000 - B/op 136628376 B/op 136631552 B/op 1.00
BenchmarkDocument/text_edit_gc_1000 - allocs/op 210672 allocs/op 210701 allocs/op 1.00
BenchmarkDocument/text_split_gc_100 - ns/op 5345353 ns/op 4598879 ns/op 1.16
BenchmarkDocument/text_split_gc_100 - B/op 2217699 B/op 2217588 B/op 1.00
BenchmarkDocument/text_split_gc_100 - allocs/op 16591 allocs/op 16589 allocs/op 1.00
BenchmarkDocument/text_split_gc_1000 - ns/op 448620897 ns/op 361375128 ns/op 1.24
BenchmarkDocument/text_split_gc_1000 - B/op 214859133 B/op 214859917 B/op 1.00
BenchmarkDocument/text_split_gc_1000 - allocs/op 211436 allocs/op 211446 allocs/op 1.00
BenchmarkDocument/text_delete_all_10000 - ns/op 22779502 ns/op 19578579 ns/op 1.16
BenchmarkDocument/text_delete_all_10000 - B/op 5904166 B/op 5904621 B/op 1.00
BenchmarkDocument/text_delete_all_10000 - allocs/op 41122 allocs/op 41128 allocs/op 1.00
BenchmarkDocument/text_delete_all_100000 - ns/op 269420542 ns/op 254618054 ns/op 1.06
BenchmarkDocument/text_delete_all_100000 - B/op 53884920 B/op 53839944 B/op 1.00
BenchmarkDocument/text_delete_all_100000 - allocs/op 415984 allocs/op 415993 allocs/op 1.00
BenchmarkDocument/text_100 - ns/op 400556 ns/op 330774 ns/op 1.21
BenchmarkDocument/text_100 - B/op 118215 B/op 118213 B/op 1.00
BenchmarkDocument/text_100 - allocs/op 5074 allocs/op 5074 allocs/op 1
BenchmarkDocument/text_1000 - ns/op 4359109 ns/op 3639084 ns/op 1.20
BenchmarkDocument/text_1000 - B/op 1152838 B/op 1152823 B/op 1.00
BenchmarkDocument/text_1000 - allocs/op 50078 allocs/op 50078 allocs/op 1
BenchmarkDocument/array_1000 - ns/op 2138088 ns/op 1797916 ns/op 1.19
BenchmarkDocument/array_1000 - B/op 1102887 B/op 1102821 B/op 1.00
BenchmarkDocument/array_1000 - allocs/op 11868 allocs/op 11867 allocs/op 1.00
BenchmarkDocument/array_10000 - ns/op 24734718 ns/op 20787542 ns/op 1.19
BenchmarkDocument/array_10000 - B/op 9906760 B/op 9907150 B/op 1.00
BenchmarkDocument/array_10000 - allocs/op 120717 allocs/op 120719 allocs/op 1.00
BenchmarkDocument/array_gc_100 - ns/op 224331 ns/op 183158 ns/op 1.22
BenchmarkDocument/array_gc_100 - B/op 98149 B/op 98169 B/op 1.00
BenchmarkDocument/array_gc_100 - allocs/op 1243 allocs/op 1243 allocs/op 1
BenchmarkDocument/array_gc_1000 - ns/op 2408174 ns/op 1976201 ns/op 1.22
BenchmarkDocument/array_gc_1000 - B/op 1170449 B/op 1170344 B/op 1.00
BenchmarkDocument/array_gc_1000 - allocs/op 12906 allocs/op 12906 allocs/op 1
BenchmarkDocument/counter_1000 - ns/op 362580 ns/op 285428 ns/op 1.27
BenchmarkDocument/counter_1000 - B/op 198536 B/op 198534 B/op 1.00
BenchmarkDocument/counter_1000 - allocs/op 6503 allocs/op 6503 allocs/op 1
BenchmarkDocument/counter_10000 - ns/op 3873018 ns/op 3095974 ns/op 1.25
BenchmarkDocument/counter_10000 - B/op 2165465 B/op 2165456 B/op 1.00
BenchmarkDocument/counter_10000 - allocs/op 69510 allocs/op 69510 allocs/op 1
BenchmarkDocument/object_1000 - ns/op 2547268 ns/op 1901665 ns/op 1.34
BenchmarkDocument/object_1000 - B/op 1450924 B/op 1451351 B/op 1.00
BenchmarkDocument/object_1000 - allocs/op 9914 allocs/op 9915 allocs/op 1.00
BenchmarkDocument/object_10000 - ns/op 30298064 ns/op 23966450 ns/op 1.26
BenchmarkDocument/object_10000 - B/op 12370494 B/op 12371453 B/op 1.00
BenchmarkDocument/object_10000 - allocs/op 101222 allocs/op 101226 allocs/op 1.00
BenchmarkRPC/client_to_server - ns/op 531785511 ns/op 401201105 ns/op 1.33
BenchmarkRPC/client_to_server - B/op 12477384 B/op 12262480 B/op 1.02
BenchmarkRPC/client_to_server - allocs/op 177260 allocs/op 177181 allocs/op 1.00
BenchmarkRPC/client_to_client_via_server - ns/op 835273778 ns/op 685664009 ns/op 1.22
BenchmarkRPC/client_to_client_via_server - B/op 22568968 B/op 22584304 B/op 1.00
BenchmarkRPC/client_to_client_via_server - allocs/op 330222 allocs/op 330481 allocs/op 1.00
BenchmarkRPC/attach_large_document - ns/op 1899917210 ns/op 1473754095 ns/op 1.29
BenchmarkRPC/attach_large_document - B/op 1809983576 B/op 1819078872 B/op 1.00
BenchmarkRPC/attach_large_document - allocs/op 9798 allocs/op 9660 allocs/op 1.01
BenchmarkRPC/adminCli_to_server - ns/op 726158534 ns/op 559190893 ns/op 1.30
BenchmarkRPC/adminCli_to_server - B/op 20397388 B/op 20410700 B/op 1.00
BenchmarkRPC/adminCli_to_server - allocs/op 321620 allocs/op 321614 allocs/op 1.00
BenchmarkLocker - ns/op 153.7 ns/op 123.1 ns/op 1.25
BenchmarkLocker - B/op 16 B/op 16 B/op 1
BenchmarkLocker - allocs/op 1 allocs/op 1 allocs/op 1
BenchmarkLockerParallel - ns/op 155.8 ns/op 126.5 ns/op 1.23
BenchmarkLockerParallel - B/op 0 B/op 0 B/op NaN
BenchmarkLockerParallel - allocs/op 0 allocs/op 0 allocs/op NaN
BenchmarkLockerMoreKeys - ns/op 528.1 ns/op 440.8 ns/op 1.20
BenchmarkLockerMoreKeys - B/op 13 B/op 13 B/op 1
BenchmarkLockerMoreKeys - allocs/op 0 allocs/op 0 allocs/op NaN
BenchmarkSync/memory_sync_10_test - ns/op 9731 ns/op 7461 ns/op 1.30
BenchmarkSync/memory_sync_10_test - B/op 1282 B/op 1283 B/op 1.00
BenchmarkSync/memory_sync_10_test - allocs/op 38 allocs/op 38 allocs/op 1
BenchmarkSync/memory_sync_100_test - ns/op 86142 ns/op 69442 ns/op 1.24
BenchmarkSync/memory_sync_100_test - B/op 8692 B/op 8763 B/op 0.99
BenchmarkSync/memory_sync_100_test - allocs/op 276 allocs/op 280 allocs/op 0.99
BenchmarkSync/memory_sync_1000_test - ns/op 794166 ns/op 699971 ns/op 1.13
BenchmarkSync/memory_sync_1000_test - B/op 81802 B/op 81716 B/op 1.00
BenchmarkSync/memory_sync_1000_test - allocs/op 2578 allocs/op 2579 allocs/op 1.00
BenchmarkSync/memory_sync_10000_test - ns/op 8467377 ns/op 7491717 ns/op 1.13
BenchmarkSync/memory_sync_10000_test - B/op 858195 B/op 850438 B/op 1.01
BenchmarkSync/memory_sync_10000_test - allocs/op 26962 allocs/op 26946 allocs/op 1.00
BenchmarkTextEditing - ns/op 31870316237 ns/op 27697786985 ns/op 1.15
BenchmarkTextEditing - B/op 8457234016 B/op 8457437952 B/op 1.00
BenchmarkTextEditing - allocs/op 20615674 allocs/op 20616814 allocs/op 1.00

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.