Skip to content

Commit 4cd2cfd

Browse files
committed
fix(model-datastructure): performance of LinearHistory
Some of the tests in ReplicatedRepositoryTest took up to several minutes. Now they all finish a few seconds. Also added a test that runs in a single coroutine to get reproducible results. This actually fixes MODELIX-524
1 parent ed2c119 commit 4cd2cfd

File tree

3 files changed

+424
-134
lines changed

3 files changed

+424
-134
lines changed

model-datastructure/src/commonMain/kotlin/org/modelix/model/LinearHistory.kt

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import org.modelix.model.lazy.IDeserializingKeyValueStore
55
import org.modelix.model.lazy.KVEntryReference
66
import org.modelix.model.persistent.CPVersion
77

8-
class LinearHistory(val baseVersionHash: String?) {
8+
/**
9+
* Was introduced in https://github.com/modelix/modelix/commit/19c74bed5921028af3ac3ee9d997fc1c4203ad44
10+
* together with the UndoOp. The idea is that an undo should only revert changes if there is no other change that relies
11+
* on it. In that case the undo should do nothing to not indirectly undo newer changes.
12+
* For example, if you added a node and someone else started changing properties on the that node, your undo should not
13+
* remove the node to not lose the property changes.
14+
*/
15+
class SlowLinearHistory(val baseVersionHash: String?) {
916

1017
val version2descendants: MutableMap<Long, MutableSet<Long>> = HashMap()
1118
val versions: MutableMap<Long, CLVersion> = HashMap()
@@ -21,7 +28,7 @@ class LinearHistory(val baseVersionHash: String?) {
2128
var result: List<Long> = ArrayList()
2229

2330
for (version in versions.values.filter { !it.isMerge() }.sortedBy { it.id }) {
24-
val descendantIds = version2descendants[version.id]!!.sorted()
31+
val descendantIds = version2descendants[version.id]!!.sorted().toSet()
2532
val idsInResult = result.toHashSet()
2633
if (idsInResult.contains(version.id)) {
2734
result =
@@ -61,3 +68,73 @@ class LinearHistory(val baseVersionHash: String?) {
6168
return CLVersion(hash.getValue(store), store)
6269
}
6370
}
71+
72+
class LinearHistory(val baseVersionHash: String?) {
73+
74+
val version2directDescendants: MutableMap<Long, Set<Long>> = HashMap()
75+
val versions: MutableMap<Long, CLVersion> = HashMap()
76+
77+
/**
78+
* Oldest version first
79+
*/
80+
fun load(vararg fromVersions: CLVersion): List<CLVersion> {
81+
for (fromVersion in fromVersions) {
82+
collect(fromVersion, null)
83+
}
84+
85+
var result: List<Long> = ArrayList()
86+
87+
for (version in versions.values.filter { !it.isMerge() }.sortedBy { it.id }) {
88+
val descendantIds = HashSet<Long>().also { collectAllDescendants(version.id, it) }.filter { !versions[it]!!.isMerge() }.sorted().toSet()
89+
val idsInResult = result.toHashSet()
90+
if (idsInResult.contains(version.id)) {
91+
result =
92+
result +
93+
descendantIds.filter { !idsInResult.contains(it) }
94+
} else {
95+
result =
96+
result.filter { !descendantIds.contains(it) } +
97+
version.id +
98+
result.filter { descendantIds.contains(it) } +
99+
descendantIds.filter { !idsInResult.contains(it) }
100+
}
101+
}
102+
return result.map { versions[it]!! }
103+
}
104+
105+
private fun collectAllDescendants(ancestor: Long, result: MutableSet<Long>, visited: MutableSet<Long> = HashSet()) {
106+
if (!visited.add(ancestor)) return
107+
val descendants = version2directDescendants[ancestor] ?: return
108+
109+
result += descendants
110+
descendants.forEach {
111+
collectAllDescendants(it, result, visited)
112+
}
113+
}
114+
115+
private fun collect(version: CLVersion, descendant: CLVersion?) {
116+
if (version.getContentHash() == baseVersionHash) return
117+
if (descendant != null) {
118+
version2directDescendants[version.id] = (version2directDescendants[version.id] ?: emptySet()) + setOf(descendant.id)
119+
}
120+
if (versions.containsKey(version.id)) return
121+
122+
versions[version.id] = version
123+
124+
if (version.isMerge()) {
125+
val version1 = getVersion(version.data!!.mergedVersion1!!, version.store)
126+
val version2 = getVersion(version.data!!.mergedVersion2!!, version.store)
127+
collect(version1, version)
128+
collect(version2, version)
129+
} else {
130+
val previous = version.baseVersion
131+
if (previous != null) {
132+
collect(previous, version)
133+
}
134+
}
135+
}
136+
137+
private fun getVersion(hash: KVEntryReference<CPVersion>, store: IDeserializingKeyValueStore): CLVersion {
138+
return CLVersion(hash.getValue(store), store)
139+
}
140+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) 2023.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.modelix.model.LinearHistory
18+
import org.modelix.model.VersionMerger
19+
import org.modelix.model.lazy.CLTree
20+
import org.modelix.model.lazy.CLVersion
21+
import org.modelix.model.lazy.ObjectStoreCache
22+
import org.modelix.model.operations.IOperation
23+
import org.modelix.model.persistent.MapBaseStore
24+
import kotlin.test.Test
25+
import kotlin.test.assertEquals
26+
27+
class LinearHistoryTest {
28+
val initialTree = CLTree.builder(ObjectStoreCache(MapBaseStore())).repositoryId("LinearHistoryTest").build()
29+
30+
@Test
31+
fun simpleTest1() {
32+
val v20 = version(20, null)
33+
val v21 = version(21, null)
34+
35+
assertHistory(v20, v21, listOf(v20, v21))
36+
}
37+
38+
@Test
39+
fun simpleTest2() {
40+
val v10 = version(10, null)
41+
val v20 = version(20, v10)
42+
val v21 = version(21, v10)
43+
44+
assertHistory(v20, v21, listOf(v20, v21))
45+
}
46+
47+
@Test
48+
fun linearHistoryPerformance() {
49+
val v30000003a = version(12884901946, null)
50+
val v1000004d1 = version(4294968529, v30000003a)
51+
val v1000004d3 = version(4294968531, v1000004d1)
52+
val v200000353 = version(8589935443, v1000004d3)
53+
val v1000004d5 = version(4294968533, v1000004d3)
54+
val v30000003c = merge(12884901948, v1000004d3, v200000353, v1000004d5)
55+
val v1000004d6 = merge(4294968534, v1000004d3, v1000004d5, v200000353)
56+
val v30000003d = merge(12884901949, v1000004d3, v30000003c, v1000004d6)
57+
val v30000003e = merge(12884901950, v1000004d3, v30000003d, v1000004d6)
58+
val v200000354 = merge(8589935444, v1000004d3, v200000353, v30000003d)
59+
val v30000003f = merge(12884901951, v1000004d3, v30000003e, v200000354)
60+
val v300000040 = merge(12884901952, v1000004d3, v30000003f, v200000354)
61+
val v200000356 = version(8589935446, v200000354)
62+
val v300000041 = merge(12884901953, v1000004d3, v300000040, v200000356)
63+
val v1000004d8 = version(4294968536, v1000004d6)
64+
val v300000042 = merge(12884901954, v1000004d3, v300000041, v1000004d8)
65+
val v1000004d9 = merge(4294968537, v1000004d3, v1000004d8, v300000041)
66+
val v300000043 = merge(12884901955, v1000004d3, v300000042, v1000004d9)
67+
val v300000044 = merge(12884901956, v1000004d3, v300000043, v1000004d9)
68+
val v200000357 = merge(8589935447, v1000004d3, v200000356, v300000041)
69+
val v300000045 = merge(12884901957, v1000004d3, v300000044, v200000357)
70+
val v300000046 = merge(12884901958, v1000004d3, v300000045, v200000357)
71+
val v1000004da = merge(4294968538, v1000004d3, v1000004d9, v300000046)
72+
val v300000047 = merge(12884901959, v1000004d3, v300000046, v1000004da)
73+
val v300000048 = merge(12884901960, v1000004d3, v300000047, v1000004da)
74+
val v200000359 = version(8589935449, v200000357)
75+
val v300000049 = merge(12884901961, v1000004d3, v300000048, v200000359)
76+
val v1000004dc = version(4294968540, v1000004da)
77+
val v30000004a = merge(12884901962, v1000004d3, v300000049, v1000004dc)
78+
val v20000035a = merge(8589935450, v1000004d3, v200000359, v300000046)
79+
val v30000004b = merge(12884901963, v1000004d3, v30000004a, v20000035a)
80+
val v30000004c = merge(12884901964, v1000004d3, v30000004b, v20000035a)
81+
val v1000004dd = merge(4294968541, v1000004d3, v1000004dc, v30000004c)
82+
val v30000004d = merge(12884901965, v1000004d3, v30000004c, v1000004dd)
83+
val v30000004e = merge(12884901966, v1000004d3, v30000004d, v1000004dd)
84+
val v20000035b = merge(8589935451, v1000004d3, v20000035a, v30000004c)
85+
val v30000004f = merge(12884901967, v1000004d3, v30000004e, v20000035b)
86+
val v300000050 = merge(12884901968, v1000004d3, v30000004f, v20000035b)
87+
val v1000004df = version(4294968543, v1000004dd)
88+
val v300000051 = merge(12884901969, v1000004d3, v300000050, v1000004df)
89+
val v20000035d = version(8589935453, v20000035b)
90+
val v300000052 = merge(12884901970, v1000004d3, v300000051, v20000035d)
91+
val v1000004e0 = merge(4294968544, v1000004d3, v1000004df, v300000051)
92+
val v300000053 = merge(12884901971, v1000004d3, v300000052, v1000004e0)
93+
val v300000054 = merge(12884901972, v1000004d3, v300000053, v1000004e0)
94+
val v20000035f = version(8589935455, v20000035d)
95+
val v300000055 = merge(12884901973, v1000004d3, v300000054, v20000035f)
96+
val v200000360 = merge(8589935456, v1000004d3, v20000035f, v300000052)
97+
val v300000056 = merge(12884901974, v1000004d3, v300000055, v200000360)
98+
val v300000057 = merge(12884901975, v1000004d3, v300000056, v200000360)
99+
val v1000004e2 = version(4294968546, v1000004e0)
100+
val v300000058 = merge(12884901976, v1000004d3, v300000057, v1000004e2)
101+
val v200000362 = version(8589935458, v200000360)
102+
val v300000059 = merge(12884901977, v1000004d3, v300000058, v200000362)
103+
val v1000004e3 = merge(4294968547, v1000004d3, v1000004e2, v300000058)
104+
val v30000005a = merge(12884901978, v1000004d3, v300000059, v1000004e3)
105+
val v30000005b = merge(12884901979, v1000004d3, v30000005a, v1000004e3)
106+
val v1000004e5 = version(4294968549, v1000004e3)
107+
val v30000005c = merge(12884901980, v1000004d3, v30000005b, v1000004e5)
108+
val v200000363 = merge(8589935459, v1000004d3, v200000362, v300000058)
109+
val v30000005d = merge(12884901981, v1000004d3, v30000005c, v200000363)
110+
val v30000005e = merge(12884901982, v1000004d3, v30000005d, v200000363)
111+
val v200000365 = version(8589935461, v200000363)
112+
val v30000005f = merge(12884901983, v1000004d3, v30000005e, v200000365)
113+
val v1000004e7 = version(4294968551, v1000004e5)
114+
val v300000060 = merge(12884901984, v1000004d3, v30000005f, v1000004e7)
115+
val v200000367 = version(8589935463, v200000365)
116+
val v300000061 = merge(12884901985, v1000004d3, v300000060, v200000367)
117+
val v1000004e9 = version(4294968553, v1000004e7)
118+
val v300000062 = merge(12884901986, v1000004d3, v300000061, v1000004e9)
119+
val v1000004ea = merge(4294968554, v1000004d3, v1000004e9, v300000060)
120+
val v300000063 = merge(12884901987, v1000004d3, v300000062, v1000004ea)
121+
val v300000064 = merge(12884901988, v1000004d3, v300000063, v1000004ea)
122+
val v200000369 = version(8589935465, v200000367)
123+
val v300000065 = merge(12884901989, v1000004d3, v300000064, v200000369)
124+
val v20000036a = merge(8589935466, v1000004d3, v200000369, v300000060)
125+
val v300000066 = merge(12884901990, v1000004d3, v300000065, v20000036a)
126+
val v1000004eb = merge(4294968555, v1000004d3, v1000004ea, v300000061)
127+
val v300000067 = merge(12884901991, v1000004d3, v300000066, v1000004eb)
128+
val v20000036c = version(8589935468, v20000036a)
129+
val v300000068 = merge(12884901992, v1000004d3, v300000067, v20000036c)
130+
val v300000069 = merge(12884901993, v1000004d3, v300000068, v20000036a)
131+
val v30000006b = merge(12884901995, v1000004d3, v300000069, v1000004eb)
132+
val v20000036d = merge(8589935469, v1000004d3, v20000036c, v300000063)
133+
val v30000006c = merge(12884901996, v1000004d3, v30000006b, v20000036d)
134+
val v30000006d = merge(12884901997, v1000004d3, v30000006c, v20000036d)
135+
val v1000004ec = merge(4294968556, v1000004d3, v1000004eb, v30000006c)
136+
val v30000006e = merge(12884901998, v1000004d3, v30000006d, v1000004ec)
137+
val v300000070 = merge(12884902000, v1000004d3, v30000006e, v1000004ec)
138+
val v20000036e = merge(8589935470, v1000004d3, v20000036d, v30000006e)
139+
val v300000071 = merge(12884902001, v1000004d3, v300000070, v20000036e)
140+
val v1000004ed = merge(4294968557, v1000004d3, v1000004ec, v30000006e)
141+
val v300000072 = merge(12884902002, v1000004d3, v300000071, v1000004ed)
142+
val v20000036f = merge(8589935471, v1000004d3, v20000036e, v30000006e)
143+
val v300000073 = merge(12884902003, v1000004d3, v300000072, v20000036f)
144+
val v300000074 = merge(12884902004, v1000004d3, v300000073, v20000036f)
145+
val v300000075 = merge(12884902005, v1000004d3, v300000074, v20000036e)
146+
val v1000004ee = merge(4294968558, v1000004d3, v30000006e, v300000075)
147+
148+
// val expected = SlowLinearHistory(v1000004d3.getContentHash()).load(v300000075, v1000004ee)
149+
val expected = listOf(
150+
v1000004d5,
151+
v200000353,
152+
v1000004d8,
153+
v1000004dc,
154+
v1000004df,
155+
v1000004e2,
156+
v1000004e5,
157+
v1000004e7,
158+
v1000004e9,
159+
v200000356,
160+
v200000359,
161+
v20000035d,
162+
v20000035f,
163+
v200000362,
164+
v200000365,
165+
v200000367,
166+
v200000369,
167+
v20000036c,
168+
)
169+
assertHistory(v300000075, v1000004ee, expected)
170+
}
171+
172+
private fun assertHistory(v1: CLVersion, v2: CLVersion, expected: List<CLVersion>) {
173+
val actual = history(v1, v2)
174+
assertEquals(expected.map { it.id.toString(16) }, actual.map { it.id.toString(16) })
175+
assertEquals(expected, actual)
176+
}
177+
178+
private fun history(v1: CLVersion, v2: CLVersion): List<CLVersion> {
179+
val base = VersionMerger.commonBaseVersion(v1, v2)
180+
return LinearHistory(base?.getContentHash()).load(v1, v2)
181+
}
182+
183+
private fun version(id: Long, base: CLVersion?): CLVersion {
184+
return CLVersion.createRegularVersion(
185+
id,
186+
null,
187+
null,
188+
initialTree,
189+
base,
190+
emptyArray(),
191+
)
192+
}
193+
194+
private fun merge(id: Long, v1: CLVersion, v2: CLVersion): CLVersion {
195+
return merge(id, VersionMerger.Companion.commonBaseVersion(v1, v2)!!, v1, v2)
196+
}
197+
198+
private fun merge(id: Long, base: CLVersion, v1: CLVersion, v2: CLVersion): CLVersion {
199+
return CLVersion.createAutoMerge(
200+
id,
201+
initialTree,
202+
base,
203+
v1,
204+
v2,
205+
emptyArray<IOperation>(),
206+
initialTree.store,
207+
)
208+
}
209+
}

0 commit comments

Comments
 (0)