When I run my Kotlin iOS app on an iPad 6, doing s...
# ios
r
When I run my Kotlin iOS app on an iPad 6, doing specific actions lead to it getting killed by the OS with an OOM error. The app was killed while using 1.5GB of RAM with 10MB left available on the device. When running the app in an arm64 simulator on my Mac M1, doing the same actions and more, I can barely make the ram usage spike to 130MB, it seems to stay under 100MB. Where do I go from there? Using the "Instruments" app I found these lines and I don't know how to know what gets allocated in there in Kotlin that takes so much space and stay allocated
When running in the simulator I can't find any allocation with the category "VM: IOAccelerator" 🤔
So by looking at the stacktrace of each of these 256MB allocations, I was able to see they were all mostly identical. By looking around the located code, I found some dumb code. By fixing it I don't seem to be able to reproduce the OOM. I think I'm lucky to have "one big issue" rather than having to really observe actual memory usage, because I still have no idea how/if we can do that
The dumb code was basically pretty printing json before logging it in my http client, and the custom "fast" pretty printer was recreating a StringBuilder dozens of times to remove some suffix, instead of using
StringBuilder.setLength
. There is only one
StringBuilder
var
that gets replaced though, so I'm not sure how it could lead to this kind of memory usage. It's somehow not freed
Maybe it's allocating memory too fast for any kind of GC to run or something. I still have a hard time understanding how that could result in 1.5GB memory usage instead of 100-150MB but oh well
p
Literally just ran into this the other day, 2 secs, I’ll find the docs
Copy code
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile).all {
    kotlinOptions.freeCompilerArgs += [
        "-Xallocator=std"
    ]
}
The new memory model uses a different allocator which preallocates a bunch of memory. We had a similar setup with multiple frameworks included, each one allocating 256mb at launch.
r
Thanks. I wonder why the default is what it is
p
I think the new allocator and the memory pool is meant to be a perf improvement. I don’t think multiple modules is the suggested setup. The other thing to consider id that each module probably has its own GC, with its own stop the world, mark and sweep process which is potentially problematic for CPU. I don’t know that thats the case but it makes sense given that each module gets its own pool. Were investigating moving to an umbrella module
r
I only have one Kotlin framework, which is my entire app. I only have Kotlin code
I didn't know having multiple Kotlin modules worked, when I first started using KMP it didn't. I think each module was coming with it's very own stdlib etc, it just wasn't a thing supposed to work
p
That’s very interesting. Does the responsible library column have multiple entries?
It works, its a pain in the ass 😄
r
On my screenshot is just 5 allocations of 256MB for the same "lib"… 😅
p
Thats a little different than what we saw, I wonder why you see multiple allocations 🤔
r
Because it really wanted to allocate 1.3GB of RAM
p
I expect the same fix to work, its the same call and I imagine the stack trace is probably the same as what I was seeing
r
I think I fixed the underlying issue, aka the code which was allocating far too much stuff. But when I run my app I don't see a single 256MB allocation which is weird, I don't know when that is supposed to occur
In my case the stacktrace contained my own code at some point
But yeah the top part is the same
p
huh, maybe not the same issue 😅
looks the same 😄
r
My code was creating a LOT of Strings I think
I don't know where they are stored but I guess when THAT is full it used this IOAccelerator thing
p
Interesting hypothesis, Might be the case
r
It's prettifying json for logging, without actually parsing it so just String -> String using a StringBuilder. I was recreating the StringBuilder every time I wanted to remove characters at the end of it, instead of using
setLength
. So it basically recreated it thousands of times, dealing with payloads of a up to a few MBs… 💥
Could do 2 of these 256MB allocation to log one payload 😬