Is there no multiplatform way to catch a `StackOve...
# getting-started
y
Is there no multiplatform way to catch a
StackOverflowError
?
d
I feel like catching SO is a code smell anyway.
💯 4
y
That's fair. I have an algorithm that needs a stack. For small sizes, I can just use the call stack. When it gets too large, though, I can switch to an explicit stack. The size is unknown beforehand sadly.
d
Maybe use DeepRecursiveFunction?
Either way, I would probably just code for the worst case, not have two implementations.
l
I have what I believe is a valid usecase for wanting to catch
StackOverflowError
. My project is a programming language interpreter, and a function call in the language maps to a Kotlin function call, meaning that infinite recursion in the language causes infinite recursion in Kotlin. There is a limit to how deep you can recurse, but for each frame, there can be 10-20 function calls on the Kotlin side. I also want to allow users to extend the recursion depth by increasing the JVM stack size without having to calculate how that maps to the language recursion. The interpreter can also be compiled to native Linux as well as JS, so a proper platform-independent way to handle this would be useful.
y
For such a project, @loke, the overhead of using
DeepRecursiveFunction
or similar should be minimal compared to the workload
l
Sadly, no. That requires me to use suspend functions, and I actually tried turning the interpreter into using
suspend
everywhere it's needed. It caused a 30% performance hit.
😲 1
At least 30%, I think it hit 50% at times.
And this is without using DRF. It's just the overhead of suspend calls vs. regular calls.
y
DRF is different though because it uses
@RestrictsSuspension
or something like that, which reduces overhead by having less fields in continuation classes.
Also, were you using any kotlinx-coroutines functions? They can slow things down in tight loops like that
Any chance this is open-source? I'd love to investigate!
l
None. All I did was add
suspend
everything until it compiled, and then ran it. I didn't actually do any suspensions anywhere.
So the test was purely on the impact of adding
suspend
, so I'm assuming the time comes from contrsuction and passing fof the continuations.
y
Hmmm, well again that would add more overhead than
RestrictsSuspension
because of extra fields allocated for those continuations. Also, some aggressive
inline
-ing and such may help
l
Basically, I started by adding
suspend
to a low level function that I wanted to suspend. Then I added it to upstream functions until I managed to get the entry point of the interpreter to run. And then I ran that in
runBlocking
and compared performance.
Yes, this test was done maybe 2 years ago. I've worked a lot on opimisation since then, and now I'm at a point where I've inlined certain things to gain maybe 1 percent or so.
Going back to double-digit performance impact would be painful, even though having it all suspendable would be really neat. I'm kinda limited by not having it.
y
Another thing is, if you tried it with Java 8 or something, the allocation cost was more expensive in that version. Modern JVMs are way faster at allocation, especially if you use the Parallel GC collector for instance, and fix a specific heap size. I myself gained at least 20% in a micro-benchmark from making these changes in a
suspend
project
l
The test was done with Java 17 at the time. Since then I've moved to 21.
👍🏼 1
I managed to get a 5% performance gain by not using a
ThreadLocal
to find the current stack, instead I used
ScopedValue
to store the current frame pointer.
ScopedValue
is not even stable in Java yet, but I had to use it, because... 5% 🙂
But thanks for your input. I think I will try to shift everything to suspendable again once Java 25 is out, at which time I'll migrate my project. Then I'll see if the performance hit is acceptable.
y
Also, Java is getting its own continuations, which might? fair a bit better here. Experimenting with that might be a good idea then (they manipulate the runtime stack directly I believe)
l
Well, perhaps. I did try to use them, primarily in the hope that their implementation of
ThreadLocal
was better (it's not, it still requires a hash lookup every time you access a value), and to be honest I didn't really find them particularly interesting. The benefit of
suspend
would be that I can programmatically manage the stack frames, and do stuff like letting a script debug itself.
Another benefit is that the JS version would be able to call suspendable API's easily, which is not possible today (I have built a real monster that uses
ShareArrayBuffer
and
Atomics
in order to create a way to block for activities. It's pretty horrific, but it's JS after all, so horrific is to be expected. Here's the code if you dare to look at it 🙂 https://codeberg.org/loke/array/src/branch/master/kap-util/src/jsMain/kotlin/com/dhsdevelopments/kap/js-transfer-queue.kt#L304
e
a "stackless" design (where the interpreted language stack is not tied to the host language stack) is IMO the only sane way you'll be able to achieve deeper call stacks in an interpreter written in safe portable code when the host language has call stack limitations https://langdev.stackexchange.com/q/755