RFR: 8339290: Optimize ClassFile Utf8EntryImpl#writeTo [v4]

Claes Redestad redestad at openjdk.org
Mon Sep 2 14:06:43 UTC 2024


On Fri, 30 Aug 2024 19:39:32 GMT, Shaojin Wen <swen at openjdk.org> wrote:

>> Use fast path for ascii characters 1 to 127 to improve the performance of writing Utf8Entry to BufferWriter.
>
> Shaojin Wen has updated the pull request incrementally with one additional commit since the last revision:
> 
>   optimize Utf8EntryImpl#writeTo(UTF)

So this small win now comes from reserving space up front (possibly excessively so) and writing directly to the byte-array, avoiding excessive `reserveSpace` calls - right? A bit underwhelming, maybe.

I also think there's a pre-existing bug here: the max length _in bytes_ of a UTF-8 classfile entry is 65535, but we're checking `s.length() < 65535` (which is only correct if the string is ascii). Perhaps this code need to do what `DataOutputStream.writeUTF(String)` does and calculate the length in bytes up front.

Did you profile the microbenchmark to see that the `Utf8EntryImpl#writeTo` is actually a bottleneck in this test? I took a peek and it seems to be doing mostly other things, and when I've run this test a few times it seems there's quite a bit of run-to-run variance. I think we need a more focused microbenchmark to say anything about the writeUTF method.

A quick, easy and portable profiling technique that doesn't depend on any external tools: `make test TEST="micro:Utf8EntryWriteTo" MICRO="OPTIONS=-p charType=ascii -f 1 -i 50 -prof jfr"` (bumping -i to get enough samples, -f 1 since the test runner would overwrite recordings from multiple forks). 

The run will just take a recording and print something like:

JFR profiler results:
  <path>/org.openjdk.bench.java.lang.classfile.Utf8EntryWriteTo.writeTo-AverageTime-charType-ascii/profile.jfr


Then use JMC or the built-in jfr tool to inspect the recording. E.g. `build/<arch>/images/jdk/bin/jfr view hot-methods <path>/org.openjdk.bench.java.lang.classfile.Utf8EntryWriteTo.writeTo-AverageTime-charType-ascii//profile.jfr` is quite useful:

                                          Java Methods that Executes the Most

Method                                                                                                  Samples Percent
------------------------------------------------------------------------------------------------------- ------- -------
jdk.internal.classfile.impl.SplitConstantPool.entryByIndex(int)                                             280   8,03%
jdk.internal.classfile.impl.RawBytecodeHelper.rawNext()                                                     263   7,54%
jdk.internal.util.ArraysSupport.unsignedHashCode(int, byte[], int, int)                                     215   6,17%
jdk.internal.classfile.impl.EntryMap.firstToken(int)                                                        210   6,02%
jdk.internal.classfile.impl.StackMapGenerator.detectFrameOffsets()                                          165   4,73%
java.lang.String.equals(Object)                                                                             165   4,73%
jdk.internal.classfile.impl.SplitConstantPool.findEntry(int, AbstractPoolEntry, AbstractPoolEntry)          109   3,13%
jdk.internal.classfile.impl.SplitConstantPool.findEntry(int, AbstractPoolEntry)                             104   2,98%
jdk.internal.classfile.impl.StackMapGenerator.processMethod()                                                85   2,44%
java.lang.classfile.constantpool.ConstantPoolBuilder.nameAndTypeEntry(String, MethodTypeDesc)                83   2,38%
java.lang.String.<init>(byte[], byte)                                                                        76   2,18%
jdk.internal.classfile.impl.SplitConstantPool.map()                                                          72   2,07%
jdk.internal.classfile.impl.BufWriterImpl.reserveSpace(int)                                                  69   1,98%
jdk.internal.classfile.impl.AbstractPoolEntry$Utf8EntryImpl.toString()                                       64   1,84%
jdk.internal.classfile.impl.SplitConstantPool.tryFindUtf8(int, String)                                       63   1,81%
jdk.internal.classfile.impl.StackMapGenerator.processInvokeInstructions(...)                                 57   1,64%
java.nio.Buffer.checkIndex(int)                                                                              54   1,55%
java.lang.classfile.constantpool.ConstantPoolBuilder.classEntry(ClassDesc)                                   53   1,52%
jdk.internal.classfile.impl.SplitConstantPool$2.fetchElement(int)                                            52   1,49%
jdk.internal.classfile.impl.AbstractPoolEntry$Utf8EntryImpl.hashCode()                                       49   1,41%
java.lang.classfile.CodeBuilder.invoke(Opcode, ClassDesc, String, MethodTypeDesc, boolean)                   48   1,38%
jdk.internal.classfile.impl.AbstractPoolEntry.maybeClone(ConstantPoolBuilder, PoolEntry)                     46   1,32%
jdk.internal.classfile.impl.SplitConstantPool.maybeCloneUtf8Entry(Utf8Entry)                                 45   1,29%
jdk.internal.classfile.impl.StackMapGenerator.processBlock(RawBytecodeHelper)                                40   1,15%
java.util.Arrays.copyOfRange(byte[], int, int)                                                               40   1,15%```

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

PR Comment: https://git.openjdk.org/jdk/pull/20772#issuecomment-2322975213
PR Comment: https://git.openjdk.org/jdk/pull/20772#issuecomment-2323337370


More information about the core-libs-dev mailing list