https://kotlinlang.org logo
#ksp
Title
# ksp
e

elihart

11/09/2023, 6:33 PM
I’ve started to notice some very long KSP task times in our app, and I’m wondering if anyone else has similar problems or insight into the root cause. One task in particular is taking up to 9 minutes (!!) on CI. However, sometimes it is as short as 1-2 minutes. There seems to be a huge variability in time. This is the ksp task for our root application module, which depends on all of the other modules in our project (~1800). Given that, it does have a lot to process, but it seems like it should be more consistent. Our builds are ephemeral, so daemon’s aren’t reused, but build cache is. I’m wondering if perhaps these long ksp times happen if there is extreme memory pressure. We give the kotlin daemon 50gb, but it is quite hard to get visibility into the memory usage of kotlin/ksp. Does anyone have suggestions for where I might want to look to optimize this? I’m hoping k2 helps of course, but it seems like there is something else going on here as well
1
t

Ting-Yuan Huang

11/09/2023, 7:00 PM
Does increasing / decreasing heap size make it harder / easier to reproduce?
e

elihart

11/09/2023, 7:03 PM
I could try decreasing the heap we give it and see what happens. we’re already maxing out our machines so i can’t give it much more unfortunately
I would really love a way to see kotlin/ksp memory usage and pressure in gradle build scans 😍
t

Ting-Yuan Huang

11/09/2023, 7:05 PM
e

elihart

11/09/2023, 7:12 PM
I’ve used it before to profile incremental behavior, but my understanding is that it doesn’t show any information about memory usage or ksp. Am I wrong about that? From the sample output on that page it doesn’t look to me like that information would be very helpful for debugging ksp performance
is there any way within ksp to see the time that each individual processor runs for?
t

Ting-Yuan Huang

11/09/2023, 10:14 PM
Not currently. It'll be quicker to add in the processors if you have access to most of them.
👍 1
e

elihart

11/10/2023, 9:26 PM
I’ve found that the culprit seems to be Dagger. We started using Dagger via KSP from its recent release, but performance could potentially be an issue for us now. We do give it lots of heap, and I haven’t verified yet to what extent the issue is worse when there is memory pressure. As a workaround, we leverage dagger reflect in most builds to avoid the codegen. If I have time I’ll profile the dagger ksp step and try to report to dagger owners
🙏 1
t

Ting-Yuan Huang

11/10/2023, 9:45 PM
If you were running dagger via kapt, there could be added heap pressure after switching to ksp. IIRC kapt runs in process isolation in gradle worker. Moving to ksp means moving to KotlinCompileDaemon and it has to share the heap with other compilation tasks, because they can run in parallel. This may also explain why it is not steadily reproducible.
System-wise, the memory usage should go down, but the pressure for KotlinCompileDaemon is larger.
Nevertheless, needing more than 50GB may still imply something wrong. Please keep me in the loop.
e

elihart

11/10/2023, 9:54 PM
thank you!
k

kaeawc

11/11/2023, 1:17 AM
What JVM settings are you using besides heap? 50 GB sounds like too much (unless Android lint is in the mix)
The 30-47GB range is wasted due to non compressed memory allocations, so in reality your build is operating with closer to 33GB ram heap. Also, shouldn't be more than 8-12 GB per daemon. Beyond that you pay crazy penalties even before the 30GB hurdle
Try running JDK19, or better 21 with G1GC on at least Gradle 8.4. Should be able to get memory footprint under control. SoftRefs 1 and metaspace size 512-1024mb
e

elihart

11/13/2023, 5:55 PM
thanks for the pointers, I appreciate it. These are the settings we use for a 32 core, 120GB machine on CI
Copy code
-Dorg.gradle.jvmargs="-Xmx30720m -Xss8m -Dkotlin.daemon.jvmargs=Xmx49830m -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=67 -XX:G1MaxNewSizePercent=67 -XX:MaxMetaspaceSize=8192m -XX:ReservedCodeCacheSize=128m"
for lint we give more heap to the gradle daemon than kotlin daemon. I’m not familiar at all with the memory waste you mentioned due to non compressed memory, but if we do less memory we get OOMs and daemons randomly dying; 12gb wouldn’t work at all for us. We have a lot of code unfortunately. We’re on jdk 17, but hopefully will be on 21 before long. I’ll read up on the settings you suggested 🙏
k

kaeawc

11/13/2023, 6:15 PM
I’d recommend watching https://www.infoq.com/presentations/JVM-Performance-Tuning-twitter/ Specifically at 13:09 he talks about compressed object pointers and the memory waste I’m referring to. Once you go past 32GB RAM you can’t use compressed pointers anymore. The lack of compression means you need more memory to handle your memory allocations and thus the big jump to 48GB
SoftRefLRUPolicyMSPerMB=0 is probably slowing your builds down a little bit since it means any soft refs will be immediately released. We saw some speed improvement going with
1
over
0
At 50GB heap it means those soft refs could be kept around for 50 seconds
You probably want MetaspaceSize=1GB at least - it defaults to 20MB so you’re probably dealing with some metaspace GCs more than you should need to
e

elihart

11/13/2023, 6:21 PM
we have
XX:MaxMetaspaceSize=8192m
doing that already, right?
k

kaeawc

11/13/2023, 6:21 PM
JDK 17 -> 19 was a pretty great perf improvement for all algos (parallel + g1gc). JDK 20 landed some good things for G1GC https://tschatzl.github.io/2023/03/14/jdk20-g1-parallel-gc-changes.html
MaxMetaspaceSize and MetaspaceSize are different
👍 1
😅 1
e

elihart

11/13/2023, 6:22 PM
thanks a ton for the pointers, I’ll try those out
🙌 1
^ that blog has 5 articles that are really great for learning about metaspace and the compresses oops/class space and how they all interact, how to measure, etc
e

elihart

11/13/2023, 6:25 PM
I appreciate it, taking a look 👀
I’m curious, do you use any tools for measuring memory usage in the kotlin daemon and gradle works in CI builds? We’ve felt pretty blind with gradle build scans, since we can only see the main gradle daemon heap usage. We’ve thought about building our own tooling to run in our CI machines to monitor memory usage of all java processes, but haven’t built that yet
k

kaeawc

11/13/2023, 6:32 PM
So, I thought about this a lot because all I really had for years was CircleCI memory usage which really tells you nothing, but I was a solo dev so I never had time to look at better tooling. Stuff I’ve been looking at recently is

https://github.com/gradle/gcc2speedscope/blob/master/speedscope.png

and https://github.com/gradle/gradle-profiler. Haven’t been able to get the former working and honestly don’t see the reason to anymore now that I have config cache reliably working on CI. The latter has been interesting because it has a chrome-tracer plugin that Jake blogged about years ago. Got that working in my Android CI test repo this weekend and I imagine exploring the memory usage overtime matched up with the Gradle task tracing could be helpful.
😍 1
Otherwise, yeah probably something custom that shunts
jcmp
output to internal observability tooling would be best
1
The last article in that metaspace series talks about analyzing it with jcmp - can also use it for native memory like LinkedIn did
e

elihart

11/13/2023, 6:35 PM
cool, yeah, that’s what we had in mind to do eventually
4 Views