Skip to content

Commit

Permalink
Improve performance for creating crdt.TreeNode (#939)
Browse files Browse the repository at this point in the history
The time complexity of creating crdt.TreeNode is O(N^2), potentially
causing performance bottlenecks. It's optimized to O(n).

While this may not be a significant issue currently, there is a risk
that as the number of tree nodes in the protobuf increases, operations
will scale quadratically, potentially causing performance bottlenecks.

---------

Co-authored-by: JiHwan Yim <[email protected]>
Co-authored-by: Youngteac Hong <[email protected]>
  • Loading branch information
3 people committed Jul 24, 2024
1 parent b494fa2 commit 379ee54
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 7 deletions.
11 changes: 4 additions & 7 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,18 +583,15 @@ func FromTreeNodes(pbNodes []*api.TreeNode) (*crdt.TreeNode, error) {
}

root := nodes[len(nodes)-1]
depthTable := make(map[int32]*crdt.TreeNode)
depthTable[pbNodes[len(nodes)-1].Depth] = nodes[len(nodes)-1]
for i := len(nodes) - 2; i >= 0; i-- {
var parent *crdt.TreeNode
for j := i + 1; j < len(nodes); j++ {
if pbNodes[i].Depth-1 == pbNodes[j].Depth {
parent = nodes[j]
break
}
}
var parent *crdt.TreeNode = depthTable[pbNodes[i].Depth-1]

if err := parent.Prepend(nodes[i]); err != nil {
return nil, err
}
depthTable[pbNodes[i].Depth] = nodes[i]
}

root.Index.UpdateDescendantsSize()
Expand Down
68 changes: 68 additions & 0 deletions test/bench/tree_editing_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//go:build bench

/*
* Copyright 2024 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package bench

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/yorkie-team/yorkie/api/converter"
"github.com/yorkie-team/yorkie/pkg/document/crdt"
"github.com/yorkie-team/yorkie/pkg/document/json"
"github.com/yorkie-team/yorkie/test/helper"
)
func BenchmarkTree(b *testing.B) {
verticesCounts := []int{10000, 20000, 30000}

for _, cnt := range verticesCounts {
root := buildTree(cnt)
b.ResetTimer()

b.Run(fmt.Sprintf("%d vertices to protobuf", cnt), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = converter.ToTreeNodes(root)
}
})

b.Run(fmt.Sprintf("%d vertices from protobuf", cnt), func(b *testing.B) {
for i := 0; i < b.N; i++ {
pbNodes := converter.ToTreeNodes(root)
_, err := converter.FromTreeNodes(pbNodes)
assert.NoError(b, err)
}
})
}
}

// buildTree creates a tree with the given number of vertices.
func buildTree(vertexCnt int) *crdt.TreeNode {
children := make([]json.TreeNode, vertexCnt)
for i := 0; i < vertexCnt; i++ {
children[i] = json.TreeNode{
Type: "p", Children: []json.TreeNode{{Type: "text", Value: "a"}},
}
}

return helper.BuildTreeNode(&json.TreeNode{
Type: "r",
Children: children,
})
}

0 comments on commit 379ee54

Please sign in to comment.