| Kotlin light-classes |
| === |
| |
| Note: this documentation has a good chance of becoming obsolete as JetBrains makes changes to the Kotlin plugin. |
| For example, Kotlin plugin version 1.3.50 is enabling "ultra-light" classes by default, which might change some of the performance |
| characteristics discussed here. |
| |
| Kotlin light classes are only loosely related to "light R classes" and similar features of Android Studio (see |
| [android-light-classes.md](android-light-classes.md)). Both mechanisms borrow the name from `LightElement`, which is a supertype for PSI |
| elements not backed by actual source code (i.e. not created by a `PsiParser`), but implement the idea in very different ways. |
| |
| Background |
| --- |
| When the IDE uses the Kotlin compiler to parse Kotlin files, the result is a bunch of KtElements (e.g., KtClass, KtConstructor, etc.). |
| However, KtElements do not really implement the full PSI interface. For example, KtClass does not implement PsiClass, and KtFunction does |
| not implement PsiMethod. |
| |
| Still, lots of code in the platform depends on resolving to and using PsiClass, PsiMethod, etc. So, to make this work, the Kotlin plugin |
| wraps KtElements with KtLightElements (also known as Kotlin light-classes). KtLightClass implements PsiClass, KtLightMethod implements |
| PsiMethod, etc. |
| |
| The package name for Kotlin light-classes is telling: “org.jetbrains.kotlin.asJava” |
| |
| Focusing on KtLightClass for a moment, there are basically two ways that it answers queries against the PsiClass interface: |
| |
| 1. If it’s easy to do, it just retrieves the information from the underlying KtElement. For example, see |
| KtLightClassForSourceDeclaration.isInterface() |
| |
| 2. Otherwise, it delegates to the Kotlin compiler to create the Java stub for the class, and then uses _that_ to answer the query. (Remember: |
| the Kotlin compiler already has to be able to create Java stubs when compiling a mixed Java/Kotlin project in order for Kotlin classes |
| to be visible to javac. So, Kotlin light-classes can delegate to the compiler for convenience.) For an example of this, see |
| KtLightClassForSourceDeclaration.getSupers(), which delegates to its `clsDelegate`. |
| |
| Q: Why are light-classes so slow? |
| --- |
| For queries of type (1), Kotlin light-classes are fast. However, when delegating to the compiler to answer queries of type (2), things get |
| slow. For example, when KtLightClassForSourceDeclaration delegates to its `clsDelegate`, then the Kotlin compiler has to generate the Java |
| stub for that class. This results in calls to things like forceResolveAllContents() and kotlin.codegen.ClassBodyCodegen.generateBridges(), |
| which can take around 50 ms per class in my manual testing. This is why, over time, JetBrains has tried to reduce the number of times it |
| delegates to the compiler: |
| |
| 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 |
| |
| Sometimes, though, they go back to delegating to the compiler if it helps avoid complexity: |
| |
| 19db4304bd616cbc9b3abfdc60fbead6f04d7826 |
| Use clsDelegate to calculate hasModifierProperty("final") for light psi |
| https://youtrack.jetbrains.com/issue/KT-17857 |
| |
| That one actually slowed down class inheritor search quite a bit, which is why we filed KT-33250 |
| |
| Q: How can I identity when light-classes are being slow? |
| --- |
| If you’re looking at a freeze report and it contains a large stack with methods like `getJavaFileStub` or `getClsDelegate`, then it’s very |
| likely that Kotlin light-classes are to blame for the slowdown. |
| |
| Q: Are Kotlin light-classes cached? |
| --- |
| Yes, but the caches are invalidated quite frequently. In the past (and still in 1.3.41) they were invalidated on every PSI change, because |
| the platform deprecated the out-of-code-block modification tracker. More recently it seems that JetBrains is working on an out-of-code-block |
| modification tracker specifically for Kotlin; see KotlinModificationTrackerService. |
| |
| Anyway, one interesting effect of these cache invalidations is that you very frequently get fresh instances of KtLightElements. This means |
| that any user data stored in that PSI is lost! Here’s a really good example of JetBrains trying to fix a performance issue caused by this: |
| |
| 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 |
| |
| So, be careful when caching things inside PsiElements that might have come from Kotlin. |
| |
| Q: What are Kotlin ultra-light classes? |
| --- |
| Kotlin ultra-light classes were designed in direct response to the slowness of Kotlin light-classes. Their functionality is identical to |
| light-classes, but the idea is that they don’t delegate to the Kotlin compiler to generate Java stubs. To be honest I don’t know why it’s |
| another layer (as opposed to just improving the original light-classes), but alas. The first commit is at: |
| |
| ebc998d710ae275d9d91d2e53446612aec33fe86 |
| add ultra-light classes/members that work without backend in simple cases |
| |
| If successful, ultra-light classes might significantly improve Kotlin IDE support. This especially impacts queries that we do from the |
| Android plugin, such as class inheritor searches, etc. In KT-33250, for example, I found that delegating to the Kotlin compiler was making |
| class inheritor search 10x slower than it could be. |
| |
| At the moment, ultra-light classes are only generated for classes which are “not too complex” |
| (see UltraLightSupport.isTooComplexForUltraLightGeneration()). Over time JetBrains seems to be supporting more and more class complexity. |
| |
| Kotlin ultra-light classes will be enabled by default starting in Kotlin 1.3.50: |
| |
| 1b5f72bd599a3725c8b2bf3b27cd8bd49cde7987 |
| Enable ultra-light classes by default |
| https://youtrack.jetbrains.com/issue/KT-29267 |