[workspace-model] Optimize memory consumption by new VirtualFileUrlManagerImpl

GitOrigin-RevId: 97900049061297e1c32feb8c79570f4a6f0f2abf
This commit is contained in:
Mikhail Mazurkevich
2020-04-24 13:02:19 +00:00
committed by intellij-monorepo-bot
parent 325c9cf27e
commit 25af0077d0
4 changed files with 59 additions and 49 deletions
@@ -29,7 +29,7 @@ class VirtualFileNameStoreTest {
Assert.assertEquals(3, store.generateIdForName("c")) Assert.assertEquals(3, store.generateIdForName("c"))
store.removeName("b") store.removeName("b")
store.removeName("a") store.removeName("a")
Assert.assertEquals(2, store.generateIdForName("e")) Assert.assertEquals(4, store.generateIdForName("e"))
Assert.assertEquals(1, store.generateIdForName("f")) Assert.assertEquals(5, store.generateIdForName("f"))
} }
} }
@@ -1,18 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspace.ide
import org.jetbrains.annotations.TestOnly
import java.util.*
internal class IdGenerator {
private val freeIdsQueue: Queue<Int> = LinkedList()
private var generator: Int = 0
fun generateId() = freeIdsQueue.poll() ?: ++generator
fun releaseId(id: Int) = freeIdsQueue.add(id)
@TestOnly
fun clear() {
generator = 0
freeIdsQueue.clear()
}
}
@@ -1,47 +1,45 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspace.ide package com.intellij.workspace.ide
import com.intellij.util.containers.BidirectionalMap import gnu.trove.THashMap
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
internal class VirtualFileNameStore { internal class VirtualFileNameStore {
private val generator = IdGenerator() private val generator = IdGenerator()
private val nameStore = BidirectionalMap<String, IdPerCount>() private val name2IdStore = THashMap<String, IdPerCount>()
private val id2NameStore = THashMap<Int, String>()
fun generateIdForName(name: String): Int { fun generateIdForName(name: String): Int {
val idPerCount = nameStore[name] val idPerCount = name2IdStore[name]
if (idPerCount != null) { if (idPerCount != null) {
idPerCount.usageCount++ idPerCount.usageCount++
return idPerCount.id return idPerCount.id
} else { } else {
val id = generator.generateId() val id = generator.generateId()
nameStore[name] = IdPerCount(id, 1) name2IdStore[name] = IdPerCount(id, 1)
id2NameStore[id] = name
return id return id
} }
} }
fun removeName(name: String) { fun removeName(name: String) {
val idPerCount = nameStore[name] ?: return val idPerCount = name2IdStore[name] ?: return
if (idPerCount.usageCount == 1L) { if (idPerCount.usageCount == 1L) {
nameStore.remove(name) name2IdStore.remove(name)
generator.releaseId(idPerCount.id) id2NameStore.remove(idPerCount.id)
} else { } else {
idPerCount.usageCount-- idPerCount.usageCount--
} }
} }
fun getNameForId(id: Int): String? { fun getNameForId(id: Int): String? = id2NameStore[id]
val list = nameStore.getKeysByValue(IdPerCount(id, 1)) ?: return null
if (list.isEmpty()) return null
assert(list.size == 1)
return list[0]
}
fun getIdForName(name: String) = nameStore[name]?.id fun getIdForName(name: String) = name2IdStore[name]?.id
@TestOnly @TestOnly
fun clear() { fun clear() {
nameStore.clear() name2IdStore.clear()
id2NameStore.clear()
generator.clear() generator.clear()
} }
} }
@@ -58,4 +56,14 @@ private data class IdPerCount(val id: Int, var usageCount: Long) {
} }
override fun hashCode() = 31 * id.hashCode() override fun hashCode() = 31 * id.hashCode()
}
internal class IdGenerator {
private var generator: Int = 0
fun generateId() = ++generator
@TestOnly
fun clear() {
generator = 0
}
} }
@@ -4,16 +4,18 @@ package com.intellij.workspace.ide
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.SmartList
import com.intellij.workspace.api.VirtualFileUrl import com.intellij.workspace.api.VirtualFileUrl
import com.intellij.workspace.api.VirtualFileUrlManager import com.intellij.workspace.api.VirtualFileUrlManager
import gnu.trove.THashMap
import kotlin.collections.set import kotlin.collections.set
class VirtualFileUrlManagerImpl: VirtualFileUrlManager() { class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
private val idGenerator= IdGenerator() private val idGenerator= IdGenerator()
private val EMPTY_URL = VirtualFileUrl(0, this) private val EMPTY_URL = VirtualFileUrl(0, this)
private val fileNameStore = VirtualFileNameStore() private val fileNameStore = VirtualFileNameStore()
private val id2NodeMapping = HashMap<Int, FilePathNode>() private val id2NodeMapping = THashMap<Int, FilePathNode>()
private var segmentId2RootNodeMapping = mutableMapOf<Int, FilePathNode>() private var segmentId2RootNodeMapping = THashMap<Int, FilePathNode>()
companion object { companion object {
fun getInstance(project: Project): VirtualFileUrlManager = project.service<VirtualFileUrlManagerImpl>() fun getInstance(project: Project): VirtualFileUrlManager = project.service<VirtualFileUrlManagerImpl>()
@@ -82,12 +84,12 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
} }
} }
val node = latestNode.children.find { it.contentId == nameId } val node = latestNode.findChild(nameId)
if (node == null) { if (node == null) {
val nodeId = idGenerator.generateId() val nodeId = idGenerator.generateId()
val newNode = FilePathNode(nodeId, nameId, latestNode) val newNode = FilePathNode(nodeId, nameId, latestNode)
id2NodeMapping[nodeId] = newNode id2NodeMapping[nodeId] = newNode
latestNode.children.add(newNode) latestNode.addChild(newNode)
latestNode = newNode latestNode = newNode
// If it's the latest name of folder or files, save entity Id as node value // If it's the latest name of folder or files, save entity Id as node value
if (index == latestElement) return VirtualFileUrl(nodeId, this) if (index == latestElement) return VirtualFileUrl(nodeId, this)
@@ -106,27 +108,25 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
println("File not found") println("File not found")
return return
} }
if (node.children.isNotEmpty()) return if (!node.isEmpty()) return
var currentNode: FilePathNode = node var currentNode: FilePathNode = node
do { do {
val parent = currentNode.parent val parent = currentNode.parent
if (parent == null) { if (parent == null) {
if (currentNode === findRootNode(currentNode.contentId) && currentNode.children.isEmpty()) { if (currentNode === findRootNode(currentNode.contentId) && currentNode.isEmpty()) {
removeNameUsage(currentNode.contentId) removeNameUsage(currentNode.contentId)
idGenerator.releaseId(currentNode.nodeId)
id2NodeMapping.remove(currentNode.nodeId) id2NodeMapping.remove(currentNode.nodeId)
segmentId2RootNodeMapping.remove(currentNode.contentId) segmentId2RootNodeMapping.remove(currentNode.contentId)
} }
return return
} }
parent.children.remove(currentNode) parent.removeChild(currentNode)
removeNameUsage(currentNode.contentId) removeNameUsage(currentNode.contentId)
idGenerator.releaseId(currentNode.nodeId)
id2NodeMapping.remove(currentNode.nodeId) id2NodeMapping.remove(currentNode.nodeId)
currentNode = parent currentNode = parent
} while (currentNode.children.isEmpty()) } while (currentNode.isEmpty())
} }
internal fun update(oldPath: String, newPath: String) { internal fun update(oldPath: String, newPath: String) {
@@ -157,7 +157,7 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
} }
} }
latestNode.children.find { it.contentId == nameId }?.let { latestNode.findChild(nameId)?.let {
if (index == latestElement) return it if (index == latestElement) return it
latestNode = it latestNode = it
} ?: return null } ?: return null
@@ -202,7 +202,28 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
} }
private inner class FilePathNode(val nodeId: Int, val contentId: Int, val parent: FilePathNode? = null) { private inner class FilePathNode(val nodeId: Int, val contentId: Int, val parent: FilePathNode? = null) {
val children: MutableSet<FilePathNode> = mutableSetOf() private var children: MutableList<FilePathNode>? = null
internal fun findChild(nameId: Int): FilePathNode? {
// If search of child node will be slow, replace SmartList to THashSet
// For now SmartList reduce 500Kb memory on IDEA project
return children?.find { it.contentId == nameId }
}
internal fun addChild(newNode: FilePathNode) {
createChildrenList()
children!!.add(newNode)
}
internal fun removeChild(node: FilePathNode) {
children?.remove(node)
}
internal fun isEmpty() = children == null || children!!.isEmpty()
private fun createChildrenList() {
if (children == null) children = SmartList()
}
override fun toString(): String { override fun toString(): String {
val buffer = StringBuilder() val buffer = StringBuilder()
@@ -213,7 +234,7 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
private fun print(buffer: StringBuilder, prefix: String, childrenPrefix: String) { private fun print(buffer: StringBuilder, prefix: String, childrenPrefix: String) {
val name = this@VirtualFileUrlManagerImpl.fileNameStore.getNameForId(contentId) val name = this@VirtualFileUrlManagerImpl.fileNameStore.getNameForId(contentId)
buffer.append("$prefix $name\n") buffer.append("$prefix $name\n")
val iterator = children.iterator() val iterator = children?.iterator() ?: return
while (iterator.hasNext()) { while (iterator.hasNext()) {
val next = iterator.next() val next = iterator.next()
if (iterator.hasNext()) { if (iterator.hasNext()) {
@@ -225,5 +246,4 @@ class VirtualFileUrlManagerImpl: VirtualFileUrlManager() {
} }
} }
} }
} }