Reducing classes loaded by ClassFile API usage
Adam Sotona
adam.sotona at oracle.com
Thu May 2 16:40:41 UTC 2024
First thanks Claes for recent performance improvements work and findings done.
Unfortunately, I don't see much space for reduction when looking at the interfaces exposed as the API.
Bound and unbound elements handling is way different, so beside some minor exceptions there is not a space for common implementation in a form of final classes.
Performance difference between handling bound and unbound elements is one order of magnitude for each level you dive into the class model.
I see small chance of abstract classes reduction in the implementation, however performance benefit would be questionable. Class numbers may reduce, however bytecode loaded may multiply as we would have to inline the abstractions into the individual implementation classes.
Huge performance work has been already done on unnecessary from/to String conversions, unnecessary sub-Stringing, arrays/lists cloning. A lot of work has been done on stack maps calculating algorithm and there still may be a space for improvements.
Now we are in process of static initialization footprint reduction, and it goes well (inline advertisement: guys, please don't forget to review pr/19006 and the CSR so we can make it into 23).
We may also look for internal "unsafe" access to construct some of the symbols, which now must pass through layers of checks, even constructed from constants. Parsing Strings and counting number of square brackets during bootstrap for hard-coded constants is pure wasting of CPU cycles... and they may be more such improvement places, or better implementation of the existing code.
In terms of loaded class numbers, we could not compare with ASM, where the visitors API approach is so different, and the most frequently used symbol is String.
Personally (and after so many different prototypes), I suggest continuing in the API evolution, rather than revolution.
Thanks,
Adam
From: classfile-api-dev <classfile-api-dev-retn at openjdk.org> on behalf of Claes Redestad <claes.redestad at oracle.com>
Date: Thursday, 2 May 2024 at 15:12
To: Brian Goetz <brian.goetz at oracle.com>
Cc: classfile-api-dev at openjdk.org <classfile-api-dev at openjdk.org>
Subject: Re: Reducing classes loaded by ClassFile API usage
I’m curious what data we have on which cases of online transformations are common, and which of those common use cases are most performance-sensitive?
Regardless, I’m just looking for constructive ways to reduce the bootstrap overheads of the API. What we have here today is getting close to being acceptable, but we would be looking at a multitude of regressions if #17108 is integrated..
2 maj 2024 kl. 14:04 skrev Brian Goetz <brian.goetz at oracle.com>:
The benchmark that we used most frequently when writing the library was the null adaptation benchmark, where we visit a class file with a transform that just passes the elements through. It is a measure of the cost to traverse and inflate the representation.
We prioritize the case where a class file is transformed with only small changes because this is one of the most common cases for online transformation.
Sent from my iPad
On May 2, 2024, at 7:58 AM, Claes Redestad <claes.redestad at oracle.com> wrote:
A performance loss where exactly?
For classfile generation (and reflection) I’d be more concerned with cold-to-lukewarm cases of getting an app up and running than, say, the number you might get in synthetic benchmarks running the API at peak performance.
2 maj 2024 kl. 13:50 skrev Brian Goetz <brian.goetz at oracle.com>:
For what it’s worth, there was an earlier experiment that merged the bound and unbound representation classes, and you could see a measurable performance loss just because these classes have more fields and there were more branches to access them.
Sent from my iPad
On May 2, 2024, at 7:34 AM, Claes Redestad <claes.redestad at oracle.com> wrote:
Hi,
Looking at replacing ASM with the ClassFile API (CFA) in various places we have observed both startup and footprint regressions. Startup times increase 4-5 ms on Hello World, 40 ms on a small GUI app and 250ms on a larger app. So there’s both a one-off cost and a scaling factor here.
We’ve been doing some analysis and picked a lot of low-hanging fruit. Bytecode executed has been reduced to about the same level and we’ve found improvements in dependencies such as the java.lang.constant API. All good. And the number of classes loaded on a Hello World style application has dropped by about 50. Great!
Still the overall picture persists: a Hello World style application that initializes a lambda takes a wee bit longer and the footprint is decidedly. The main culprit now that some low-hanging fruit has been plucked seem to be that the trivial use of CFA to spin up lambda proxies is loading in about 160 classes: An ASM-based baseline loads 691 classes. The best recent CFA version (a merge of https://github.com/openjdk/jdk/pull/19006 and https://github.com/openjdk/jdk/pull/17108) loads 834. A net 143 class difference.
Loading classes slows down startup, increases memory footprint, grows the default CDS archive. And involving more classes - and more code - is often costly even accompanied with some of the solutions being explored to ”fix” startup at large.
So why is this?
The CFA is mainly split up into two package stuctures, one public under java.lang.classfile and one internal under jdk.internal.classfile.impl. In the public side most of the types are sealed interfaces, which are then implemented by an assortment of abstract and concrete classes under jdk.internal.classfile.impl. Very neat. But I do fear this means we are at least doubling the number of loaded classes from this neat separation.
While it’s a bit late in the game I still feel I must propose striking up a conversation about what, if anything, we could consider that would reduce the number of loaded classes. Whether they are interfaces, abstract or concrete classes. I think any savings would be very welcome.
Here’s an idea:
There are a number of cases where the separation seem unnecessary:
public sealed interface ArrayLoadInstruction extends Instruction
permits AbstractInstruction.UnboundArrayLoadInstruction {
…
static ArrayLoadInstruction of(Opcode op) {
Util.checkKind(op, Opcode.Kind.ARRAY_LOAD);
return new AbstractInstruction.UnboundArrayLoadInstruction(op);
}
}
An interface in java.lang.classfile.instruction which only permits a single implementation class - and as it happens has a static factory method which is the only place where that concrete instruction is called.
Making single-use interfaces such as this one a final class is doable[1], but now we’d have some instructions modeled as an interface, others as classes. Cats and dogs, living together. And it gets messy quick for instructions that can be bound or unbound, since those inherit from abstract BoundInstruction or UnboundInstruction respectively. But perhaps internal implementation details like whether an instruction is bound or unbound ought to be modeled with composition rather than inheritance (and optional CodeImpl + pos tuple) in a shared base class? Then it might follow that each of the interfaces in java.lang.classfile.instruction can really be a single final class. If all concrete instructions were folded into their corresponding interface that could reduce the total number of implementation classes by 46 (though only 6 of those seem to be on a Hello World)
Yikes, that’s a deep cut for a small, incremental gain. From an API consumer point of view I can’t say there’s much difference, and the factories can still (be evolved to) produce different concrete types when necessary.
Maybe someone can think of other, simpler ways to reduce the number of types floating around in the ClassFile API?
Thank you for your consideration.
Claes
[1] https://github.com/openjdk/jdk/compare/master...cl4es:jdk:fold_instruction_example?expand=1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20240502/2d96c547/attachment-0001.htm>
More information about the classfile-api-dev
mailing list