RFR: 8306842: Classfile API performance improvements

Adam Sotona asotona at openjdk.org
Tue May 9 14:15:55 UTC 2023


On Wed, 26 Apr 2023 15:04:50 GMT, Adam Sotona <asotona at openjdk.org> wrote:

> Following improvements implemented:
> - Switch over `String` replaced with switch single char
> - Binary search for frames in `StackMapGenerator`
> - `StackMapGenerator.rawHandlers` with pre-calculated offsets
> - `ClassEntry` is caching `ClassDesc` symbol
> - Caching of type symbols in `NameAndTypeEntry` and `MethodTypeEntry`
> - Caching `MethodTypeDesc` in `MethodInfo` implementations
> - `StackMapGenerator` and `Utils` delegating to cached `MethodTypeDesc` instead of custom parsing
> 
> No API change.
> 
> Benchmarks show stack map generation improved by 21% and code generation from symbols improved by 30%.
> 
> 
> Benchmark                     Mode  Cnt       Score       Error  Units
> GenerateStackMaps.benchmark  thrpt   10  407931.202 ± 13101.023  ops/s
> RebuildMethodBodies.shared   thrpt    4   10258.597 ±   383.699  ops/s
> RebuildMethodBodies.unshared thrpt    4    7224.543 ±   256.800  ops/s
> 
> 
> 
> Benchmark                     Mode  Cnt       Score      Error  Units
> GenerateStackMaps.benchmark  thrpt   10  495101.110 ± 2389.628  ops/s
> RebuildMethodBodies.shared    thrpt   4   13380.272 ±  810.113  ops/s
> RebuildMethodBodies.unshared  thrpt   4    9399.863 ±  557.060  ops/s

Caching of `ClassDesc` in `ClassEntry` is already a part of this PR and the internal name is key for `ClassEntry` from the very beginning, so obtaining internal name from `ClassEntry` was never an issue.
However `ClassEntry` is a short-live object, newly created for each generated or transformed class and it exists within a constant pool context only.
In order to reach the same performance as with cached internal names in `ClassDesc` instances, we would have to change the user coding schema and not the Classfile API internals.
Every code generation or transformation would have to start with static declaration of `ClassEntry` constants (instead of `ClassDesc`), constructed from to `TemporaryConstantPool` (which is for internal purpose yet).

I've added new benchmarks measuring repeated transformations rebuilding method bodies (down to the symbols expansion) from already expanded models.
I’ve added shared and unshared CP alternatives (for curiosity), however the unshared is closer to simulation of building from symbols.
 
Here are results from the actual code base:
 
Benchmark                      Mode  Cnt      Score     Error  Units
RebuildMethodBodies.shared    thrpt    4  10258.597 ± 383.699  ops/s
RebuildMethodBodies.unshared  thrpt    4   7224.543 ± 256.800  ops/s
 
And here is already visible approx. 22% improvement in both (without the rest of proposed improvements yet):
 
Benchmark                      Mode  Cnt      Score      Error  Units
RebuildMethodBodies.shared    thrpt    4  12498.597 ± 309.585  ops/s
RebuildMethodBodies.unshared  thrpt    4   8807.229 ±  167.247  ops/s

It would be also interesting to see a difference with cached internal names in `ClassDesc`.

Now we have following benchamrk numbers:

Benchmark                      Mode  Cnt      Score     Error  Units
RebuildMethodBodies.shared    thrpt    4  13380.272 ± 810.113  ops/s
RebuildMethodBodies.unshared  thrpt    4   9399.863 ± 557.060  ops/s


Which is 30% performance improvement :)

I benchmarked merged this PR with #13598 and benefits of caching of internal names in ClassDesc are insignificant (~2% performance boost).

Problem with ClassDesc "embedding" into the Utf8Entry is with its ambiguity.
Utf8Entry mainly contains some name or descriptor (MethodTypeDesc, ClassDesc, PackageDesc, ModuleDesc, etc...) or signature (Signature, MethodSignature, ClassSignature, etc...).
And even for ClassDesc there is ambiguity of the serialized form. When the Utf8Entry is related to ClassEntry it contains descriptor for arrays or internal name for classes. However when the Utf8Entry is related for example to annotation it contains always class descriptor (even for classes).
>From this perspective the Utf8Entry should not be responsible for conversions to and from symbols, because it does not know the context and so the right serialised form.
ClassEntry knows the  exact form of its Utf8Entry, Annotation, MethodInfo or (with just a minor ambiguity) also NameAndTypeEntry know which symbols to use and how to serialize/deserialize them.
I think that having Utf8Entry as a big central dispatcher for so many types of symbols and serialisation forms is not the best design and will include a lot of complexity and confusion. 
Current architecture lets interaction with symbols responsibility on the right layer.

 It is unlikely that one Utf8Entry will hold two different symbols, however still possible (for example when Signature and ClassDesc are the same).
And it is still very likely that one symbol describing the same class will be stored in two different Utf8Entries (for example the same class stored as reference from ClassEntry vs. as reference from Annotation).

-------------

PR Comment: https://git.openjdk.org/jdk/pull/13671#issuecomment-1527068370
PR Comment: https://git.openjdk.org/jdk/pull/13671#issuecomment-1527081241
PR Comment: https://git.openjdk.org/jdk/pull/13671#issuecomment-1527241784
PR Comment: https://git.openjdk.org/jdk/pull/13671#issuecomment-1527327378
PR Comment: https://git.openjdk.org/jdk/pull/13671#issuecomment-1531104946


More information about the core-libs-dev mailing list