RFR: 8266972: Use String.concat() in j.l.Class where invokedynamic-based String concatenation is not available

Сергей Цыпанов github.com+10835776+stsypanov at openjdk.java.net
Wed May 12 13:04:21 UTC 2021


Hello, from discussion in https://github.com/openjdk/jdk/pull/3464 I've found out, that in a few of JDK core classes, e.g. in `j.l.Class` expressions like `baseName.replace('.', '/') + '/' + name` are not compiled into `invokedynamic`-based code, but into one using `StringBuilder`. This happens due to some bootstraping issues.

However the code like

private String getSimpleName0() {
    if (isArray()) {
        return getComponentType().getSimpleName() + "[]";
    }
    //...
}

can be improved via replacement of `+` with `String.concat()` call.

I've used this benchmark to measure impact:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ClassMethodsBenchmark {
  private final Class<? extends Object[]> arrayClass = Object[].class;
  private Method descriptorString;

  @Setup
  public void setUp() throws NoSuchMethodException {
    //fore some reason compiler doesn't allow me to call Class.descriptorString() directly
    descriptorString = Class.class.getDeclaredMethod("descriptorString");
  }

  @Benchmark
  public Object descriptorString() throws Exception {
    return descriptorString.invoke(arrayClass);
  }

  @Benchmark
  public Object typeName() {
    return arrayClass.getTypeName();
  }
}

and got those results

                                                   Mode  Cnt     Score     Error   Units
descriptorString                                   avgt   60    91.480 ±   1.839   ns/op
descriptorString:·gc.alloc.rate.norm               avgt   60   404.008 ±   4.033    B/op
descriptorString:·gc.churn.G1_Eden_Space           avgt   60  2791.866 ± 181.589  MB/sec
descriptorString:·gc.churn.G1_Eden_Space.norm      avgt   60   401.702 ±  26.047    B/op
descriptorString:·gc.churn.G1_Survivor_Space       avgt   60     0.003 ±   0.001  MB/sec
descriptorString:·gc.churn.G1_Survivor_Space.norm  avgt   60    ≈ 10⁻³              B/op
descriptorString:·gc.count                         avgt   60   205.000            counts
descriptorString:·gc.time                          avgt   60   216.000                ms

patched
                                                   Mode  Cnt     Score     Error   Units
descriptorString                                   avgt   60    65.016 ±   3.446   ns/op
descriptorString:·gc.alloc.rate                    avgt   60  2844.240 ± 115.719  MB/sec
descriptorString:·gc.alloc.rate.norm               avgt   60   288.006 ±   0.001    B/op
descriptorString:·gc.churn.G1_Eden_Space           avgt   60  2832.996 ± 206.939  MB/sec
descriptorString:·gc.churn.G1_Eden_Space.norm      avgt   60   286.955 ±  17.853    B/op
descriptorString:·gc.churn.G1_Survivor_Space       avgt   60     0.003 ±   0.001  MB/sec
descriptorString:·gc.churn.G1_Survivor_Space.norm  avgt   60    ≈ 10⁻³              B/op
descriptorString:·gc.count                         avgt   60   208.000            counts
descriptorString:·gc.time                          avgt   60   228.000                ms
-----------------
typeName                                           avgt   60    34.273 ±   0.480   ns/op
typeName:·gc.alloc.rate                            avgt   60  3265.356 ±  45.113  MB/sec
typeName:·gc.alloc.rate.norm                       avgt   60   176.004 ±   0.001    B/op
typeName:·gc.churn.G1_Eden_Space                   avgt   60  3268.454 ± 134.458  MB/sec
typeName:·gc.churn.G1_Eden_Space.norm              avgt   60   176.109 ±   6.595    B/op
typeName:·gc.churn.G1_Survivor_Space               avgt   60     0.003 ±   0.001  MB/sec
typeName:·gc.churn.G1_Survivor_Space.norm          avgt   60    ≈ 10⁻⁴              B/op
typeName:·gc.count                                 avgt   60   240.000            counts
typeName:·gc.time                                  avgt   60   255.000                ms

patched

typeName                                           avgt   60    15.787 ±   0.214   ns/op
typeName:·gc.alloc.rate                            avgt   60  2577.554 ±  32.339  MB/sec
typeName:·gc.alloc.rate.norm                       avgt   60    64.001 ±   0.001    B/op
typeName:·gc.churn.G1_Eden_Space                   avgt   60  2574.039 ± 147.774  MB/sec
typeName:·gc.churn.G1_Eden_Space.norm              avgt   60    63.895 ±   3.511    B/op
typeName:·gc.churn.G1_Survivor_Space               avgt   60     0.003 ±   0.001  MB/sec
typeName:·gc.churn.G1_Survivor_Space.norm          avgt   60    ≈ 10⁻⁴              B/op
typeName:·gc.count                                 avgt   60   189.000            counts
typeName:·gc.time                                  avgt   60   199.000                ms

I think this patch is likely to improve reflection operations related to arrays.

If one finds this patch useful, then I'll create a ticket to track this. Also it'd be nice to have a list of classes, that are compiled in the same way as `j.l.Class` (i.e. have no access to `invokedynamic`) so I could look into them for other snippets where two String are joined so `concat`-based optimization is possible.

Pre-sizing of `StringBuilder` for `Class.gdescriptorString()` and `Class.getCanonicalName0()` is one more improvement opportunity here.

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

Commit messages:
 - Use String.concat() where invokedynamic-based String concatenation is not available

Changes: https://git.openjdk.java.net/jdk/pull/3627/files
 Webrev: https://webrevs.openjdk.java.net/?repo=jdk&pr=3627&range=00
  Issue: https://bugs.openjdk.java.net/browse/JDK-8266972
  Stats: 4 lines in 1 file changed: 0 ins; 0 del; 4 mod
  Patch: https://git.openjdk.java.net/jdk/pull/3627.diff
  Fetch: git fetch https://git.openjdk.java.net/jdk pull/3627/head:pull/3627

PR: https://git.openjdk.java.net/jdk/pull/3627


More information about the core-libs-dev mailing list