Introduce reflection cache in KClassImpl and KPackageImpl
Most of KClassImpl operations should be cached, but creating a lazy value for each operation would significantly increase memory footprint of a KClassImpl instance. Therefore we decide to store all lazy values under a single lazy value named 'data' which is stored in KClassImpl. There's a minor overhead of indirection on any operation now, however it'll allow us to substantially increase reflection performance by caching everything we can
This commit is contained in:
@@ -20,28 +20,48 @@ import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
class ReflectionCodeSanityTest : TestCase() {
|
||||
fun testNoDelegatedPropertiesInKClassAndKProperties() {
|
||||
val classLoader = ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
|
||||
private lateinit var classLoader: ClassLoader
|
||||
|
||||
val classesToCheck = linkedSetOf<Class<*>>()
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
classLoader = ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
ReflectionCodeSanityTest::classLoader.javaField!!.set(this, null)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
private fun loadClass(name: String): Class<*> =
|
||||
classLoader.loadClass("kotlin.reflect.jvm.internal.$name")
|
||||
|
||||
private fun collectClassesWithSupers(vararg names: String): Set<Class<*>> {
|
||||
val result = linkedSetOf<Class<*>>()
|
||||
fun addClassToCheck(klass: Class<*>) {
|
||||
if (classesToCheck.add(klass)) {
|
||||
@Suppress("UNNECESSARY_SAFE_CALL") // https://youtrack.jetbrains.com/issue/KT-9294
|
||||
klass.superclass?.let { addClassToCheck(it) }
|
||||
if (result.add(klass)) {
|
||||
klass.superclass?.let(::addClassToCheck)
|
||||
}
|
||||
}
|
||||
|
||||
fun addClass(name: String) = addClassToCheck(classLoader.loadClass("kotlin.reflect.jvm.internal.$name"))
|
||||
for (name in names) {
|
||||
addClassToCheck(loadClass(name))
|
||||
}
|
||||
|
||||
addClass("KClassImpl")
|
||||
addClass("KMutableProperty0Impl")
|
||||
addClass("KMutableProperty1Impl")
|
||||
addClass("KMutableProperty2Impl")
|
||||
return result
|
||||
}
|
||||
|
||||
fun testNoDelegatedPropertiesInKClassAndKProperties() {
|
||||
val badFields = linkedSetOf<Field>()
|
||||
for (klass in classesToCheck) {
|
||||
for (klass in collectClassesWithSupers(
|
||||
"KClassImpl",
|
||||
"KMutableProperty0Impl",
|
||||
"KMutableProperty1Impl",
|
||||
"KMutableProperty2Impl"
|
||||
)) {
|
||||
badFields.addAll(klass.declaredFields.filter { it.name.endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) })
|
||||
}
|
||||
|
||||
@@ -54,4 +74,35 @@ class ReflectionCodeSanityTest : TestCase() {
|
||||
badFields.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fun testMaxAllowedFields() {
|
||||
// The following classes are instantiated a lot in Kotlin applications and thus they should be optimized as best as possible.
|
||||
// This test checks that these classes have not more fields than a predefined small number, which can usually be calculated as
|
||||
// the number of constructor parameters (number of objects needed to initialize an instance) + 1 for 'data', the reflection cache.
|
||||
val classesWithMaxAllowedFields = linkedMapOf(
|
||||
"KClassImpl" to 2, // jClass, data
|
||||
"KPackageImpl" to 3 // jClass, moduleName, data
|
||||
)
|
||||
|
||||
val badClasses = linkedMapOf<Class<*>, Collection<Field>>()
|
||||
for ((className, maxAllowedFields) in classesWithMaxAllowedFields) {
|
||||
val klass = loadClass(className)
|
||||
val fields = generateSequence(klass) { it.superclass }
|
||||
.flatMap { it.declaredFields.asSequence() }
|
||||
.filterNot { Modifier.isStatic(it.modifiers) }
|
||||
.toList()
|
||||
if (fields.size > maxAllowedFields) {
|
||||
badClasses[klass] = fields
|
||||
}
|
||||
}
|
||||
|
||||
if (badClasses.isNotEmpty()) {
|
||||
fail("Some classes in reflection.jvm contain more fields than it is allowed. Please optimize storage in these classes:\n\n" +
|
||||
badClasses.entries.joinToString("\n") { entry ->
|
||||
val (klass, fields) = entry
|
||||
"$klass has ${fields.size} fields but max allowed = ${classesWithMaxAllowedFields[klass.simpleName]}:\n" +
|
||||
fields.joinToString("\n") { " $it" }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user