**The ask:** An (optional) compiler flag reporting the emitted bytecode size of each method.
The compiler must have this somewhere when it writes the code out?
Possibly : A scalafix to check it
The Journey
More or less revolves around my hobby [vecxt]( GitHub - Quafadas/vecxt · GitHub ) which is me exploring and learning about arrays, matrices etc.
It starts with Array[A]. You’ll quickly notice catastrophic boxing… and you’ll also quickly notice, that you can inline round it… I’m posting (the obvious) concrete sum extension method for illustration.
inline def sumSIMD: Double =
var i: Int = 0
var acc = DoubleVector.zero(spd)
while i < spd.loopBound(vec.length) do
acc = acc.add(DoubleVector.fromArray(spd, vec, i))
i += spdl
end while
var temp = acc.reduceLanes(VectorOperators.ADD)
// var temp = 0.0
while i < vec.length do
temp += vec(i)
i += 1
end while
temp
end sumSIMD
At first, I believed the inline looks like an asymmetric bet. Make all the things inline and don’t worry about generics / specialisation…
What happens next, was not obvious to me. Here’s the key JVM default info…
| Bytecodes | Threshold | What happens above it |
|---|---|---|
| 35 | MaxInlineSize |
Not inlined at cold call sites; still inlined if the site is hot. |
| 325 | FreqInlineSize |
Not inlined into callers even when hot. Method is still JIT-compiled and fully optimised internally; what’s lost is optimisation across the boundary — escape analysis, constant propagation, loop fusion. |
| 8000 | DontCompileHugeMethods |
Never JIT-compiled at all. Runs interpreted for the life of the process, silently, with nothing in a profile pointing at the cause. |
| 65535 | JVMS §4.7.3 code_length |
Compile error — “Method too large”. |
That method above? Something like 80 bytecodes, because you have two loops and the SIMD instructions… similar for many methods like +, * etc.
If you start inlining these together in a chain? I got this so badly wrong, that I blew out the 65535 limit
when trying to mess around with a neural network type thing. Which was my first hint that something… wasn’t quite right.
HugeMethodLimit is the danger and a nasty performance cliff - “Great microbenchmarks, horrible real world performance” wasn’t the tagline I had in mind for vecxt!
`-Xmax-inlines` (default 32) is the only guard rail I’m aware of. It fires on the right axis with the wrong framing. Its message names *successive* inlines and suggests a recursive inline method. Mine not recursion, but rather a pathological, aggressively layered inlining strategy in which I was confident there was no recursion. The message states no consequence and prints the flag to raise the limit, so I raised it. Wording like *“raising this limit may produce methods too large for the JIT to compile (>8000 bytecodes) and may materially impact JVM performance”* would, I hope, have triggered an alarm bell…
## Documentation
The macros best-practices page does say to avoid generating large methods for JIT reasons — one sentence and no numbers. The inline tutorial frames inlining as a metaprogramming entrypoint and does not discuss size. So a fair criticism is not “undocumented”. Rather qualitative, with no thresholds or a measurement path. It is very easy to ignore.
The numbers that matter — 35, 325, 8000, 65535 … were pulled from Claude’s memory (I’ve done my best to check them, I believe they represent the JVM defaults). I’m not aware of too many people with the confidence to start tampering with them.
Essentially, I was not aware, despite a reasonable best effort and genuine ecosystem curiosity, that I was playing with fire.
## Proposals
1. **`-Vprint-method-sizes`** — emitted bytecode size per method. No new analysis needed; makes the existing best-practices advice actionable, and lets authors diff a refactor or assert a CI bound.
2. **Reword the `-Xmax-inlines` message** to name the potential consequences.
- Add a hint which has a better chance of triggering follow up questions to the
inlinescala docs.inlineis not an asymmetric, consequence free keyword - it actively eats the budget the JVM uses for it’s optimisations. - A scalafix lint, for those two know they are playing with fire and want to manage it below a level
I suspect, that I hit a particularly sharp edge as I was mucking around with both the VectorAPI and the specialisation part. However, I do wonder how many methods out there silently go over that 8000 bytecode cliff. It’s a silent performance killer, and possibly a way to lose hearts and minds.
Here’s what I think I measured in terms of bytecode output…
| ops | universally inline |
after first de-inlining | |
|---|---|---|---|
chain01 — (a * b).sum |
2 | 226 | 17 |
chain02 |
3 | 281 | 64 |
chain04 — (a * b + a - b + a).sum |
5 | 391 | 158 |
chain08 |
9 | 770 | 273 |
chain16 |
16 | 1347 | 547 |
same arithmetic as chain04, hand-written loop |
— | 47 | 47 |
Chain 16 is not unreasonable for numeric stuff. And did not appears unreasonable, to start chaining those methods together. Works great in the small, horrible as you scale it up. Hence the request for a discussion…