Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tree.Edit benchmark and improve performance #137

Merged
merged 16 commits into from
Oct 17, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import dev.yorkie.document.Document
import dev.yorkie.document.json.JsonTree
import dev.yorkie.document.json.TreeBuilder.element
import dev.yorkie.document.json.TreeBuilder.text
import dev.yorkie.document.time.TimeTicket.Companion.MaxTimeTicket
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -117,4 +121,140 @@ class DocumentBenchmark {
}
}
}

@Test
fun tree_100() {
benchmarkTree(100)
}

@Test
fun tree_1000() {
benchmarkTree(1000)
}

@Test
fun tree_delete_100() {
benchmarkTreeDeleteAll(100)
}

@Test
fun tree_delete_1000() {
benchmarkTreeDeleteAll(1000)
}

@Test
fun tree_split_gc_100() {
benchmarkTreeSplitGC(100)
}

@Test
fun tree_split_gc_1000() {
benchmarkTreeSplitGC(1000)
}

@Test
fun tree_edit_gc_100() {
benchmarkTreeEditGC(100)
}

@Test
fun tree_edit_gc_1000() {
benchmarkTreeEditGC(1000)
}

private fun benchmarkTree(size: Int) {
benchmarkRule.measureRepeated {
runTest {
val document = runWithTimingDisabled {
Document(Document.Key("d1"))
}
document.updateAsync { root, _ ->
root.setNewTree("tree", element("doc") { element("p") }).apply {
for (i in 1..size) {
edit(i, i, text { "a" })
}
}
}.await()
}
}
}

private fun benchmarkTreeDeleteAll(size: Int) {
benchmarkRule.measureRepeated {
runTest {
val document = runWithTimingDisabled {
Document(Document.Key("d1"))
}
document.updateAsync { root, _ ->
root.setNewTree("tree", element("doc") { element("p") }).apply {
for (i in 1..size) {
edit(i, i, text { "a" })
}
edit(1, size + 1)
}
}.await()
val expected = runWithTimingDisabled {
"<doc><p></p></doc>"
}
assert(document.getRoot().getAs<JsonTree>("tree").toXml() == expected)
}
}
}

private fun benchmarkTreeSplitGC(size: Int) {
benchmarkRule.measureRepeated {
runTest {
val document = runWithTimingDisabled {
Document(Document.Key("d1"))
}
document.updateAsync { root, _ ->
root.setNewTree(
"tree",
element("doc") {
element("p") {
text { "a".repeat(size) }
}
},
)
}.await()

document.updateAsync { root, _ ->
for (i in 1..size) {
root.getAs<JsonTree>("tree").edit(i, i + 1, text { "b" })
}
}.await()

assert(document.garbageLength == size)
assert(document.garbageCollect(MaxTimeTicket) == size)
assert(document.garbageLength == 0)
}
}
}

private fun benchmarkTreeEditGC(size: Int) {
benchmarkRule.measureRepeated {
runTest {
val document = runWithTimingDisabled {
Document(Document.Key("d1"))
}
document.updateAsync { root, _ ->
root.setNewTree("tree", element("doc") { element("p") }).apply {
for (i in 1..size) {
edit(i, i, text { "a" })
}
}
}.await()

document.updateAsync { root, _ ->
for (i in 1..size) {
root.getAs<JsonTree>("tree").edit(i, i + 1, text { "b" })
}
}.await()

assert(document.garbageLength == size)
assert(document.garbageCollect(MaxTimeTicket) == size)
assert(document.garbageLength == 0)
}
}
}
}
6 changes: 4 additions & 2 deletions yorkie/src/main/kotlin/dev/yorkie/document/Document.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ public class Document(public val key: Key, private val options: Options = Option
public var status = DocumentStatus.Detached
internal set

internal val garbageLength: Int
@VisibleForTesting
public val garbageLength: Int
get() = root.getGarbageLength()

internal val onlineClients = MutableStateFlow(setOf<ActorID>())
Expand Down Expand Up @@ -352,7 +353,8 @@ public class Document(public val key: Key, private val options: Options = Option
/**
* Deletes elements that were removed before the given time.
*/
internal fun garbageCollect(ticket: TimeTicket): Int {
@VisibleForTesting
public fun garbageCollect(ticket: TimeTicket): Int {
if (options.disableGC) {
return 0
}
Expand Down
8 changes: 4 additions & 4 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ internal class CrdtTree(
): List<TreeChange> {
val (fromParent, fromLeft) = findNodesAndSplitText(range.first, executedAt)
val (toParent, toLeft) = findNodesAndSplitText(range.second, executedAt)
// TODO(7hong13): check whether toPath is set correctly
val changes = listOf(
TreeChange(
type = TreeChangeType.Style,
Expand Down Expand Up @@ -272,15 +271,16 @@ internal class CrdtTree(
leftSiblingNode.insNextID = split.id
}
}
val allChildren = parentNode.allChildren
val index = if (parentNode == leftSiblingNode) {
0
} else {
parentNode.allChildren.indexOf(leftSiblingNode) + 1
allChildren.indexOf(leftSiblingNode) + 1
}

var updatedLeftSiblingNode = leftSiblingNode
for (i in index until parentNode.allChildren.size) {
val next = parentNode.allChildren[i]
val next = allChildren[i]
if (executedAt < next.id.createdAt) {
updatedLeftSiblingNode = next
} else {
Expand Down Expand Up @@ -412,7 +412,7 @@ internal class CrdtTree(
}

/**
* Converts the given [pos] to the index of the tree.
* Converts the given [parentNode] to the index of the tree.
*/
fun toIndex(parentNode: CrdtTreeNode, leftSiblingNode: CrdtTreeNode): Int {
return indexTree.indexOf(toTreePos(parentNode, leftSiblingNode))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ internal fun CrdtTreeNode.toTreeNode(): TreeNode {
return if (isText) {
TreeNode(type, value = value)
} else {
TreeNode(type, children.map { it.toTreeNode() }, attributes = attributes)
TreeNode(type, children.map(CrdtTreeNode::toTreeNode), attributes = attributes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public data class TimeTicket(
)

public val InitialTimeTicket = TimeTicket(0, INITIAL_DELIMITER, INITIAL_ACTOR_ID)
internal val MaxTimeTicket = TimeTicket(MAX_LAMPORT, MAX_DELIMITER, MAX_ACTOR_ID)
public val MaxTimeTicket = TimeTicket(MAX_LAMPORT, MAX_DELIMITER, MAX_ACTOR_ID)

public operator fun TimeTicket?.compareTo(other: TimeTicket?): Int {
return orNull().compareTo(other.orNull())
Expand Down
Loading