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.codegen.forTestCompile.ForTestCompileRuntime
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
|
||||||
class ReflectionCodeSanityTest : TestCase() {
|
class ReflectionCodeSanityTest : TestCase() {
|
||||||
fun testNoDelegatedPropertiesInKClassAndKProperties() {
|
private lateinit var classLoader: ClassLoader
|
||||||
val classLoader = ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
|
|
||||||
|
|
||||||
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<*>) {
|
fun addClassToCheck(klass: Class<*>) {
|
||||||
if (classesToCheck.add(klass)) {
|
if (result.add(klass)) {
|
||||||
@Suppress("UNNECESSARY_SAFE_CALL") // https://youtrack.jetbrains.com/issue/KT-9294
|
klass.superclass?.let(::addClassToCheck)
|
||||||
klass.superclass?.let { addClassToCheck(it) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addClass(name: String) = addClassToCheck(classLoader.loadClass("kotlin.reflect.jvm.internal.$name"))
|
for (name in names) {
|
||||||
|
addClassToCheck(loadClass(name))
|
||||||
|
}
|
||||||
|
|
||||||
addClass("KClassImpl")
|
return result
|
||||||
addClass("KMutableProperty0Impl")
|
}
|
||||||
addClass("KMutableProperty1Impl")
|
|
||||||
addClass("KMutableProperty2Impl")
|
|
||||||
|
|
||||||
|
fun testNoDelegatedPropertiesInKClassAndKProperties() {
|
||||||
val badFields = linkedSetOf<Field>()
|
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) })
|
badFields.addAll(klass.declaredFields.filter { it.name.endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,4 +74,35 @@ class ReflectionCodeSanityTest : TestCase() {
|
|||||||
badFields.joinToString("\n"))
|
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" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,18 +35,36 @@ import kotlin.reflect.*
|
|||||||
|
|
||||||
internal class KClassImpl<T : Any>(override val jClass: Class<T>) :
|
internal class KClassImpl<T : Any>(override val jClass: Class<T>) :
|
||||||
KDeclarationContainerImpl(), KClass<T>, KClassifierImpl, KAnnotatedElementImpl {
|
KDeclarationContainerImpl(), KClass<T>, KClassifierImpl, KAnnotatedElementImpl {
|
||||||
private val descriptor_ = ReflectProperties.lazySoft {
|
private inner class Data : KDeclarationContainerImpl.Data() {
|
||||||
val classId = classId
|
val descriptor: ClassDescriptor by ReflectProperties.lazySoft {
|
||||||
|
val classId = classId
|
||||||
|
val moduleData = data().moduleData
|
||||||
|
|
||||||
val descriptor =
|
val descriptor =
|
||||||
if (classId.isLocal) moduleData.deserialization.deserializeClass(classId)
|
if (classId.isLocal) moduleData.deserialization.deserializeClass(classId)
|
||||||
else moduleData.module.findClassAcrossModuleDependencies(classId)
|
else moduleData.module.findClassAcrossModuleDependencies(classId)
|
||||||
|
|
||||||
descriptor ?: reportUnresolvedClass()
|
descriptor ?: reportUnresolvedClass()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val objectInstance: T? by ReflectProperties.lazy {
|
||||||
|
val descriptor = descriptor
|
||||||
|
if (descriptor.kind != ClassKind.OBJECT) return@lazy null
|
||||||
|
|
||||||
|
val field = if (descriptor.isCompanionObject && !CompanionObjectMapping.isMappedIntrinsicCompanionObject(descriptor)) {
|
||||||
|
jClass.enclosingClass.getDeclaredField(descriptor.name.asString())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jClass.getDeclaredField(JvmAbi.INSTANCE_FIELD)
|
||||||
|
}
|
||||||
|
field.get(null) as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val descriptor: ClassDescriptor
|
private val data = ReflectProperties.lazy { Data() }
|
||||||
get() = descriptor_()
|
|
||||||
|
override val descriptor: ClassDescriptor get() = data().descriptor
|
||||||
|
|
||||||
override val annotated: Annotated get() = descriptor
|
override val annotated: Annotated get() = descriptor
|
||||||
|
|
||||||
@@ -70,7 +88,6 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) :
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun getProperties(name: Name): Collection<PropertyDescriptor> =
|
override fun getProperties(name: Name): Collection<PropertyDescriptor> =
|
||||||
(memberScope.getContributedVariables(name, NoLookupLocation.FROM_REFLECTION) +
|
(memberScope.getContributedVariables(name, NoLookupLocation.FROM_REFLECTION) +
|
||||||
staticScope.getContributedVariables(name, NoLookupLocation.FROM_REFLECTION))
|
staticScope.getContributedVariables(name, NoLookupLocation.FROM_REFLECTION))
|
||||||
@@ -123,22 +140,7 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) :
|
|||||||
jClass?.let { KClassImpl(it) }
|
jClass?.let { KClassImpl(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
override val objectInstance: T? get() = data().objectInstance
|
||||||
private val objectInstance_ = ReflectProperties.lazy {
|
|
||||||
val descriptor = descriptor
|
|
||||||
if (descriptor.kind != ClassKind.OBJECT) return@lazy null
|
|
||||||
|
|
||||||
val field = if (descriptor.isCompanionObject && !CompanionObjectMapping.isMappedIntrinsicCompanionObject(descriptor)) {
|
|
||||||
jClass.enclosingClass.getDeclaredField(descriptor.name.asString())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
jClass.getDeclaredField(JvmAbi.INSTANCE_FIELD)
|
|
||||||
}
|
|
||||||
field.get(null) as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override val objectInstance: T?
|
|
||||||
get() = objectInstance_()
|
|
||||||
|
|
||||||
override fun isInstance(value: Any?): Boolean {
|
override fun isInstance(value: Any?): Boolean {
|
||||||
// TODO: use Kotlin semantics for mutable/read-only collections once KT-11754 is supported (see TypeIntrinsics)
|
// TODO: use Kotlin semantics for mutable/read-only collections once KT-11754 is supported (see TypeIntrinsics)
|
||||||
|
|||||||
@@ -31,14 +31,13 @@ import kotlin.jvm.internal.ClassBasedDeclarationContainer
|
|||||||
import kotlin.reflect.KotlinReflectionInternalError
|
import kotlin.reflect.KotlinReflectionInternalError
|
||||||
|
|
||||||
internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer {
|
internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer {
|
||||||
// Note: this is stored here on a soft reference to prevent GC from destroying the weak reference to it in the moduleByClassLoader cache
|
protected abstract inner class Data {
|
||||||
private val moduleData_ = ReflectProperties.lazySoft {
|
// This is stored here on a soft reference to prevent GC from destroying the weak reference to it in the moduleByClassLoader cache
|
||||||
jClass.getOrCreateModule()
|
val moduleData: RuntimeModuleData by ReflectProperties.lazySoft {
|
||||||
|
jClass.getOrCreateModule()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleData: RuntimeModuleData
|
|
||||||
get() = moduleData_()
|
|
||||||
|
|
||||||
protected open val methodOwner: Class<*>
|
protected open val methodOwner: Class<*>
|
||||||
get() = jClass
|
get() = jClass
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package kotlin.reflect.jvm.internal
|
package kotlin.reflect.jvm.internal
|
||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
|
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
|
||||||
import org.jetbrains.kotlin.load.java.structure.reflect.classId
|
import org.jetbrains.kotlin.load.java.structure.reflect.classId
|
||||||
@@ -31,27 +28,33 @@ import org.jetbrains.kotlin.serialization.deserialization.descriptors.Deserializ
|
|||||||
import kotlin.reflect.KCallable
|
import kotlin.reflect.KCallable
|
||||||
|
|
||||||
internal class KPackageImpl(override val jClass: Class<*>, val moduleName: String) : KDeclarationContainerImpl() {
|
internal class KPackageImpl(override val jClass: Class<*>, val moduleName: String) : KDeclarationContainerImpl() {
|
||||||
private val descriptor = ReflectProperties.lazySoft {
|
private inner class Data : KDeclarationContainerImpl.Data() {
|
||||||
with(moduleData) {
|
val descriptor: PackageViewDescriptor by ReflectProperties.lazySoft {
|
||||||
packageFacadeProvider.registerModule(moduleName)
|
with(moduleData) {
|
||||||
module.getPackage(jClass.classId.packageFqName)
|
packageFacadeProvider.registerModule(moduleName)
|
||||||
|
module.getPackage(jClass.classId.packageFqName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodOwner: Class<*> by ReflectProperties.lazy {
|
||||||
|
val facadeName = ReflectKotlinClass.create(jClass)?.classHeader?.multifileClassName
|
||||||
|
// We need to check isNotEmpty because this is the value read from the annotation which cannot be null.
|
||||||
|
// The default value for 'xs' is empty string, as declared in kotlin.Metadata
|
||||||
|
// TODO: do not read ReflectKotlinClass multiple times, obtain facade name from descriptor
|
||||||
|
if (facadeName != null && facadeName.isNotEmpty()) {
|
||||||
|
jClass.classLoader.loadClass(facadeName.replace('/', '.'))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jClass
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val methodOwner: Class<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
private val data = ReflectProperties.lazy { Data() }
|
||||||
val facadeName = ReflectKotlinClass.create(jClass)?.classHeader?.multifileClassName
|
|
||||||
// We need to check isNotEmpty because this is the value read from the annotation which cannot be null.
|
|
||||||
// The default value for 'xs' is empty string, as declared in kotlin.Metadata
|
|
||||||
// TODO: do not read ReflectKotlinClass multiple times, obtain facade name from descriptor
|
|
||||||
if (facadeName != null && facadeName.isNotEmpty()) {
|
|
||||||
jClass.classLoader.loadClass(facadeName.replace('/', '.'))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
jClass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val scope: MemberScope get() = descriptor().memberScope
|
override val methodOwner: Class<*> get() = data().methodOwner
|
||||||
|
|
||||||
|
private val scope: MemberScope get() = data().descriptor.memberScope
|
||||||
|
|
||||||
override val members: Collection<KCallable<*>>
|
override val members: Collection<KCallable<*>>
|
||||||
get() = getMembers(scope, declaredOnly = false, nonExtensions = true, extensions = true).filter { member ->
|
get() = getMembers(scope, declaredOnly = false, nonExtensions = true, extensions = true).filter { member ->
|
||||||
|
|||||||
Reference in New Issue
Block a user