Optimize memory-footprint for RemoteLookupTrackerClient

Observations:
- In case of !requiresPosition we don't need to track position
- Also, in the latter case we don't need scopeKind (implicit contract)
- There were a lot of LookupInfo instances having two references
(fileName/scopeFqName) pointing to the same strings.
Looks like in common case (reasonable amount of files/fqnames) it's more
sensible to group lookups in the two-level-tree (implemented with nested maps)

It's expected to decrease memory consumption up to 15M in case of compiling
`idea` module in Kotlin project
This commit is contained in:
Denis Zharkov
2018-08-07 13:29:11 +07:00
parent e41e28271b
commit 16a27d593c
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.daemon
import com.intellij.util.containers.StringInterner
import gnu.trove.THashMap
import gnu.trove.THashSet
import org.jetbrains.kotlin.daemon.common.CompilerCallbackServicesFacade
import org.jetbrains.kotlin.daemon.common.DummyProfiler
@@ -27,10 +28,15 @@ import org.jetbrains.kotlin.incremental.components.Position
import org.jetbrains.kotlin.incremental.components.ScopeKind
class RemoteLookupTrackerClient(val facade: CompilerCallbackServicesFacade, eventManager: EventManager, val profiler: Profiler = DummyProfiler()) : LookupTracker {
class RemoteLookupTrackerClient(
val facade: CompilerCallbackServicesFacade,
eventManager: EventManager,
val profiler: Profiler = DummyProfiler()
) : LookupTracker {
private val isDoNothing = profiler.withMeasure(this) { facade.lookupTracker_isDoNothing() }
private val lookups = THashSet<LookupInfo>()
// Map: FileName -> (ScopeFqName -> Set<Name[String] | LookupInfo>)
private val lookups = THashMap<String, MutableMap<String, MutableSet<Any>>>()
private val interner = StringInterner()
override val requiresPosition: Boolean = profiler.withMeasure(this) { facade.lookupTracker_requiresPosition() }
@@ -38,11 +44,16 @@ class RemoteLookupTrackerClient(val facade: CompilerCallbackServicesFacade, even
override fun record(filePath: String, position: Position, scopeFqName: String, scopeKind: ScopeKind, name: String) {
if (isDoNothing) return
val internedFilePath = interner.intern(filePath)
val internedScopeFqName = interner.intern(scopeFqName)
val internedSymbolFqName = interner.intern(scopeFqName)
val internedName = interner.intern(name)
lookups.add(LookupInfo(internedFilePath, position, internedScopeFqName, scopeKind, internedName))
val objectToPut: Any =
if (requiresPosition)
LookupInfo(filePath, position, scopeFqName, scopeKind, name)
else
internedName
lookups.getOrPut(filePath, ::THashMap).getOrPut(internedSymbolFqName, ::THashSet).add(objectToPut)
}
init {
@@ -50,11 +61,24 @@ class RemoteLookupTrackerClient(val facade: CompilerCallbackServicesFacade, even
}
private fun flush() {
if (isDoNothing || lookups.isEmpty()) return
if (isDoNothing || lookups.isEmpty) return
profiler.withMeasure(this) {
// Converting to list is intentional because THashSet may lead to serialization-related problems
facade.lookupTracker_record(lookups.toList())
facade.lookupTracker_record(
lookups.flatMap { (filePath, lookupsByFile) ->
lookupsByFile.flatMap { (scopeFqName, lookupsByScopeFqName) ->
lookupsByScopeFqName.map { lookupInfoOrString ->
if (requiresPosition)
lookupInfoOrString as LookupInfo
else
LookupInfo(
filePath, Position.NO_POSITION, scopeFqName, ScopeKind.CLASSIFIER,
lookupInfoOrString as String
)
}
}
}
)
}
lookups.clear()