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:
Alexander Udalov
2016-08-17 15:56:08 +03:00
parent f6b471ac01
commit b9067e6462
4 changed files with 119 additions and 64 deletions
@@ -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" }
})
}
}
}