目录

翻译:Kotlin light-classes

Kotlin light-classes

Note: 随着 JetBrains 对 Kotlin 插件的修改,本文档很有可能会被淘汰。例如,Kotlin 插件 1.3.50 版本默认启用了 Ultra Light Class,这可能会改变这里讨论的一些性能特征。

Kotlin 的轻型类与 “light R classes” 和 Android Studio 的类似功能只有少许的关系(见android-light-classes.md)。这两种机制都借用了LightElement的名字,它是没有实际源代码支持的 PsiElement 的超类型(即不是由PsiParser创建的),但以非常不同的方式实现这一想法。

Background

当 IDE 使用 Kotlin 编译器解析 Kotlin 文件时,其结果是一堆 KtElements(如 KtClass、KtConstructor 等)。然而,KtElements 并没有真正实现完整的 PSI 接口。例如,KtClass 没有实现 PsiClass,而 KtFunction 也没有实现 PsiMethod。

不过,很多代码还是依赖于解析和使用 PsiClass、PsiMethod 等。因此,为了使其发挥作用,Kotlin 插件用KtLightElements(也称为 Kotlin light-classes)包装了 KtElements。KtLightClass 实现了查询 PsiClass,KtLightMethod 实现了 PsiMethod…

因此 Kotlin light-classes 包名就叫做: org.jetbrains.kotlin.asJava

当我们关注一下 KtLightClass,会发现它基本上有两种方式实现对 PsiClass 接口的查询

  1. 如果他只需要从底层的 KtElement 中检索信息,那么这是很容易做到的,例如 KtLightClassForSourceDeclaration.isInterface()
  2. 否则,它将委托 Kotlin 编译器为该类创建 Java stub,然后使用该 Java stub 来实现对接口的访问。(记住,在编译一个混合的 Java/Kotlin 项目时,Kotlin 编译器已经能够创建 Java stub,以便 Kotlin 类能被 javac 看到。所以,Kotlin 的 Light Class 可以委托给编译器以获得便利。关于这方面的例子,请看委托到 clsDelegate 上的 KtLightClassForSourceDeclaration.getSupers()

Q: Why are light-classes so slow?

对于(1)类型的查询,Kotlin light-classes 很快速。然而,当委托给编译器来回答(2)类型的查询时,事情就会变得很慢。例如,当 KtLightClassForSourceDeclaration 委托给它的clsDelegate时,Kotlin 编译器必须为该类生成 Java stub。这导致了对 forceResolveAllContents() 和 kotlin.codegen.ClassBodyCodegen.generateBridges() 等的调用。在我的手动测试中,每个类可能需要50毫秒左右。这就是为什么随着时间的推移,JetBrains 试图减少其委托给编译器的次数:

c00fbb236f7cab7e9a256c8c4c3fa55f105b106b
don't perform full resolve and stub building for isInheritor() checks
https://youtrack.jetbrains.com/issue/KT-8656

d74a989d9340e16662bc6b14e7c222d337db115c
Tweak light classes to avoid computing stubs on certain api calls

3cb38e7f02b3612d9f0741d0e70b3b39a57f86b2
Implement getLanguageLevel for FakeFileForLightClass
https://youtrack.jetbrains.com/issue/KT-12006

daef8a0eed08502c0aea5f14e4d1459cf8c74666
Light classes in IDE: Make light class delegate construction a two step process

不过有时如果有助于避免复杂性的话,他们会回到委托给编译器

19db4304bd616cbc9b3abfdc60fbead6f04d7826
Use clsDelegate to calculate hasModifierProperty("final") for light psi
https://youtrack.jetbrains.com/issue/KT-17857

这个实际上使类搜索其继承的父类的速度降低了不少,这也是我们提出 KT-33250 的原因

Q: How can I identity when light-classes are being slow?

如果你在看一份 freeze report,并且它包含一个大的堆栈,其中有getJavaFileStubgetClsDelegate这样的方法,那么很有可能是 Kotlin Light class 导致了速度变慢

Q: Are Kotlin light-classes cached?

是的,但是缓存经常被废止。在过去(在1.3.41中也是如此),每次PSI的变化都会使缓存失效,因为平台废除了代码块外的修改跟踪器。最近,JetBrains 似乎正在为 Kotlin 开发一个代码块外的修改跟踪器;见 KotlinModificationTrackerService

这些缓存失效的一个有趣的影响是,你会经常得到 KtLightElements 的新实例。这意味着任何存储在 PSI 中的用户数据都会丢失。这里有一个非常好的例子,JetBrains试图解决由这个问题引起的性能问题。

6ac345df516b38b4bf5ee1300626c936079f2672
Caching `KtLightClassForSourceDeclaration` (to keep user data longer)
to make their UserData survive for longer, because otherwise a new LightClass with empty UserData comes to Spring every time, but Spring stores a lot of important things in UserData
https://youtrack.jetbrains.com/issue/KT-21701

因此,当在 PsiElements 中缓存可能来自 Kotlin 的东西时要小心。

Q: What are Kotlin ultra-light classes?

Kotlin ultra-light classes 的设计是为了解决 Kotlin light-classes 的缓慢性。它们的功能与 light-classes 一样,但它们的想法是,它们不委托 Kotlin 编译器生成 Java stub。说实话,我不知道为什么它是另一个层次(而不是仅仅改进原来的 light-classes),但可惜了,第一次提交是在:

ebc998d710ae275d9d91d2e53446612aec33fe86
add ultra-light classes/members that work without backend in simple cases

如果成功的话,ultra-light classes 可能会大大改善 Kotlin IDE 的支持。这尤其影响到我们从 Android 插件进行的查询,如类的继承者搜索等。例如,在 KT-33250 中,我发现委托给 Kotlin 编译器会使类继承者搜索的速度比它能做到的慢10倍。

目前,ultra-light classes 只为 “不太复杂” 的类生成。 (见UltraLightSupport.isTooComplexForUltraLightGeneration())。随着时间的推移,JetBrains似乎会支持越来越多的类的复杂性。

Kotlin ultra-light classes 在 Kotlin 1.3.50 已经被默认启用:

1b5f72bd599a3725c8b2bf3b27cd8bd49cde7987
Enable ultra-light classes by default
https://youtrack.jetbrains.com/issue/KT-29267

原文:google android idea plugin docs