Skip to content

Commit 5e1934d

Browse files
author
Oleksandr Dzhychko
committed
perf(bulk-model-sync): use lightweight node reference instead of nodes in critical indices
An example are `MPSNode`s and `MPSModelReference`s used during importing into MPS. `MPSNode` (an `INode`) uses more memory because it references an `SNode` and prevents it from being garbage collected. `MPSModelReference` (an `INodeReference`) is much lighter because it only references `SModelReference`. `MPSModelReference` only needs to be resolved to `MPSNode` in the special case when a node was moved.
1 parent 0366e43 commit 5e1934d

File tree

2 files changed

+36
-17
lines changed
  • bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk
  • mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters

2 files changed

+36
-17
lines changed

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelImporter.kt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.modelix.model.api.SerializedNodeReference
2525
import org.modelix.model.api.getDescendants
2626
import org.modelix.model.api.isChildRoleOrdered
2727
import org.modelix.model.api.remove
28+
import org.modelix.model.api.resolveInCurrentContext
2829
import org.modelix.model.data.ModelData
2930
import org.modelix.model.data.NodeData
3031
import kotlin.jvm.JvmName
@@ -50,7 +51,13 @@ class ModelImporter(
5051
) {
5152
// We have seen imports where the `originalIdToExisting` had a dozen ten million entries.
5253
// Therefore, choose a map with is optimized for memory usage.
53-
private var originalIdToExisting = MemoryEfficientMap<String, INode>()
54+
// For the same reason store `INodeReference`s instead of `INode`s.
55+
// In a few cases, where we need the `INode` we can resolve it.
56+
private var originalIdToExisting = MemoryEfficientMap<String, INodeReference>()
57+
58+
// Use`INode` instead of `INodeReference` in `postponedReferences` and `nodesToRemove`
59+
// because we know that we will always need the `INode`s in those cases.
60+
// Those cases are deleting nodes and adding references to nodes.
5461
private val postponedReferences = mutableListOf<PostponedReference>()
5562
private val nodesToRemove = HashSet<INode>()
5663
private var numExpectedNodes = 0
@@ -109,7 +116,7 @@ class ModelImporter(
109116
buildExistingIndex(root)
110117

111118
logger.info { "Importing nodes..." }
112-
data.root.originalId()?.let { originalIdToExisting[it] = root }
119+
data.root.originalId()?.let { originalIdToExisting[it] = root.reference }
113120
syncNode(root, data.root, progressReporter)
114121

115122
logger.info { "Synchronizing references..." }
@@ -162,7 +169,7 @@ class ModelImporter(
162169
val expectedId = expected.originalId()
163170
checkNotNull(expectedId) { "Specified node '$expected' has no ID." }
164171
newChild.setPropertyValue(NodeData.idPropertyKey, expectedId)
165-
originalIdToExisting[expectedId] = newChild
172+
originalIdToExisting[expectedId] = newChild.reference
166173
syncNode(newChild, expected, progressReporter)
167174
}
168175
continue
@@ -197,13 +204,18 @@ class ModelImporter(
197204
val nodeAtIndex = existingChildren.getOrNull(newIndex)
198205
val expectedConcept = expected.concept?.let { s -> ConceptReference(s) }
199206
val childNode = if (nodeAtIndex?.originalId() != expectedId) {
200-
val existingNode = originalIdToExisting[expectedId]
201-
if (existingNode == null) {
207+
val existingNodeReference = originalIdToExisting[expectedId]
208+
if (existingNodeReference == null) {
202209
val newChild = existingParent.addNewChild(role, newIndex, expectedConcept)
203210
newChild.setPropertyValue(NodeData.idPropertyKey, expectedId)
204-
originalIdToExisting[expectedId] = newChild
211+
originalIdToExisting[expectedId] = newChild.reference
205212
newChild
206213
} else {
214+
val existingNode = existingNodeReference.resolveInCurrentContext()
215+
checkNotNull(existingNode) {
216+
// This reference should always be resolvable because the node existed or was created before.
217+
"Could not resolve $existingNodeReference."
218+
}
207219
// The existing child node is not only moved to a new index,
208220
// it is potentially moved to a new parent and role.
209221
existingParent.moveChild(role, newIndex, existingNode)
@@ -230,7 +242,7 @@ class ModelImporter(
230242

231243
private fun buildExistingIndex(root: INode) {
232244
root.getDescendants(true).forEach { node ->
233-
node.originalId()?.let { originalIdToExisting[it] = node }
245+
node.originalId()?.let { originalIdToExisting[it] = node.reference }
234246
}
235247
}
236248

mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSArea.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import jetbrains.mps.project.facets.JavaModuleFacet
1919
import jetbrains.mps.project.structure.modules.ModuleReference
2020
import jetbrains.mps.smodel.GlobalModelAccess
2121
import jetbrains.mps.smodel.SNodePointer
22+
import org.jetbrains.mps.openapi.model.SNodeReference
2223
import org.jetbrains.mps.openapi.module.SRepository
2324
import org.jetbrains.mps.openapi.persistence.PersistenceFacade
2425
import org.modelix.model.api.IBranch
@@ -54,6 +55,11 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference {
5455
}
5556

5657
override fun resolveNode(ref: INodeReference): INode? {
58+
// By far, the most common case is to resolve a MPSNodeReference.
59+
// Optimize for that case by not serializing and doing string operations.
60+
if (ref is MPSNodeReference) {
61+
return resolveSNodeReferenceToMPSNode(ref.ref)
62+
}
5763
val serialized = ref.serialize()
5864
val prefix = serialized.substringBefore(":")
5965
return when (prefix) {
@@ -138,18 +144,19 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference {
138144
}
139145

140146
private fun resolveMPSNodeReference(ref: INodeReference): MPSNode? {
141-
val sNodeReference = if (ref is MPSNodeReference) {
142-
ref.ref
143-
} else {
144-
val serialized = ref.serialize()
145-
val serializedMPSRef = when {
146-
serialized.startsWith("mps-node:") -> serialized.substringAfter("mps-node:")
147-
serialized.startsWith("mps:") -> serialized.substringAfter("mps:")
148-
else -> return null
149-
}
150-
SNodePointer.deserialize(serializedMPSRef)
147+
if (ref is MPSNodeReference) {
148+
return resolveSNodeReferenceToMPSNode(ref.ref)
151149
}
150+
val serialized = ref.serialize()
151+
val serializedMPSRef = when {
152+
serialized.startsWith("mps-node:") -> serialized.substringAfter("mps-node:")
153+
serialized.startsWith("mps:") -> serialized.substringAfter("mps:")
154+
else -> return null
155+
}
156+
return resolveSNodeReferenceToMPSNode(SNodePointer.deserialize(serializedMPSRef))
157+
}
152158

159+
private fun resolveSNodeReferenceToMPSNode(sNodeReference: SNodeReference): MPSNode? {
153160
return sNodeReference.resolve(repository)?.let { MPSNode(it) }
154161
}
155162

0 commit comments

Comments
 (0)