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 authored Jul 24, 2024
1 parent b494fa2 commit 85a1a3a
Show file tree
Hide file tree
Showing 2 changed files with 71 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
67 changes: 67 additions & 0 deletions test/bench/tree_editing_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//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 (
"testing"

"github.com/yorkie-team/yorkie/api/converter"
api "github.com/yorkie-team/yorkie/api/yorkie/v1"
"github.com/yorkie-team/yorkie/pkg/document/json"
"github.com/yorkie-team/yorkie/test/helper"
)

// bench mark converting from protobuf tree nodes to crdt.TreeNode
func BenchmarkTreeConverting(b *testing.B) {
b.Run("1000 vertex tree converting test", func(b *testing.B) {
TreeConverting(1000, b)
})

b.Run("10000 vertex tree converting test", func(b *testing.B) {
TreeConverting(10000, b)
})

b.Run("100000 vertex tree converting test", func(b *testing.B) {
TreeConverting(100000, b)
})
}

// MakeTree is a helper function to create simple tree.
func MakeTree(vertexCnt int) []*api.TreeNode {
var chd []json.TreeNode
for i := 0; i < vertexCnt; i++ {
chd = append(chd, json.TreeNode{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "a"}}})
}

root := helper.BuildTreeNode(&json.TreeNode{
Type: "r",
Children: chd,
})

pbNodes := converter.ToTreeNodes(root)
return pbNodes
}

func TreeConverting(vertexCnt int, b *testing.B) {
b.StopTimer()
pbNodes := MakeTree(vertexCnt)
b.StartTimer()

converter.FromTreeNodes(pbNodes)
}

0 comments on commit 85a1a3a

Please sign in to comment.