Backport of JDK-8054221 into JDK 8u

Сергей Цыпанов sergei.tsypanov at yandex.ru
Fri May 8 07:23:16 UTC 2020


Hello,

https://bugs.openjdk.java.net/browse/JDK-8054221 fixed in JDK9 significantly improves
performance of StringJoiner by replacing inner StringBuilder with String[].

I've backported the changes into JDK8u (patch attached) and measured performance with this benchmark:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g", "-XX:+UseParallelGC"})
public class StringJoinerBenchmark {

  @Benchmark
  public String stringJoiner(Data data) {
    return joinWithStringJoiner(data.stringArray);
  }

  private static String joinWithStringJoiner(String[] stringArray) {
    StringJoiner joiner = new StringJoiner(",", "[", "]");
    for (String str : stringArray) {
      joiner.add(str);
    }
    return joiner.toString();
  }

  @State(Scope.Thread)
  public static class Data {
    @Param({"1", "5", "10", "100"})
    private int length;
    @Param({"1", "5", "10", "100"})
    private int count;

    private String[] stringArray;

    @Setup
    public void setup() {
      stringArray = new String[count];

      String alphabet = "abcdefghijklmnopqrstuvwxyz";

      for (int i = 0; i < count; i++) {
        stringArray[i] = randomString(alphabet, length);
      }
    }

    private String randomString(String alphabet, int length) {
      char[] chars = alphabet.toCharArray();
      ThreadLocalRandom random = ThreadLocalRandom.current();

      StringBuilder sb = new StringBuilder(length);
      for (int i = 0; i < length; i++) {
        char c = chars[random.nextInt(chars.length)];
        sb.append(c);
      }
      return sb.toString();
    }
  }
}

On my machine I've got the following results (full output is available on https://gist.github.com/stsypanov/ee7483c6e4f3d5a711743a531baa049f):

Benchmark                            (count)  (length)         original           patched  Units

stringJoiner                               1         1     39.4 ±   0.5      40.4 ±   0.1  ns/op
stringJoiner                               1         5     41.1 ±   0.5      41.5 ±   0.1  ns/op
stringJoiner                               1        10     45.0 ±   1.0      42.6 ±   0.2  ns/op
stringJoiner                               1       100    111.3 ±   4.0      54.3 ±   0.1  ns/op
stringJoiner                               5         1    102.3 ±   2.9      76.3 ±   0.3  ns/op
stringJoiner                               5         5    124.9 ±   3.8      77.9 ±   0.4  ns/op
stringJoiner                               5        10    146.8 ±   1.5      84.9 ±   0.3  ns/op
stringJoiner                               5       100    425.0 ±   6.7     194.0 ±   0.7  ns/op
stringJoiner                              10         1    204.1 ±   1.3     158.1 ±   0.9  ns/op
stringJoiner                              10         5    266.1 ± 167.3     166.0 ±   0.7  ns/op
stringJoiner                              10        10    258.7 ±   5.6     179.1 ±   0.6  ns/op
stringJoiner                              10       100    932.0 ±  33.4     454.1 ±   2.2  ns/op
stringJoiner                             100         1   1771.4 ±  39.5    1360.0 ±  21.0  ns/op
stringJoiner                             100         5   2083.9 ±  42.2    1495.2 ±  23.6  ns/op
stringJoiner                             100        10   2104.1 ±  24.7    1719.9 ±  19.2  ns/op
stringJoiner                             100       100   7042.0 ± 162.9    4068.2 ± 112.8  ns/op

stringJoiner:·gc.alloc.rate.norm           1         1    168.0 ±   0.0     120.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           1         5    176.0 ±   0.0     136.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           1        10    184.0 ±   0.0     152.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           1       100   1016.0 ±   0.0     520.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           5         1    184.0 ±   0.0     152.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           5         5    312.0 ±   0.0     232.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           5        10    520.0 ±   0.0     328.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm           5       100   4328.0 ±   0.0    2136.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm          10         1    296.0 ±   0.0     280.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm          10         5    536.0 ±   0.0     440.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm          10        10    936.0 ±   0.0     632.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm          10       100   8648.0 ±   0.0    4232.0 ±   0.0   B/op
stringJoiner:·gc.alloc.rate.norm         100         1   1744.0 ±   0.0    1976.0 ±   5.4   B/op
stringJoiner:·gc.alloc.rate.norm         100         5   6032.0 ±   0.0    3576.0 ±   5.4   B/op
stringJoiner:·gc.alloc.rate.norm         100        10   7032.0 ±   0.0    5576.0 ±   5.4   B/op
stringJoiner:·gc.alloc.rate.norm         100       100  73056.0 ±   0.0   41576.0 ±   5.4   B/op


The backport demonstrates significant improvement both for average time and consumed memory.
StringJoiner is widely used in JDK and in client code (esp. when using Collectors.joining()) so I think this is a valuable improvement.

Can we apply the attached patch to upcoming version of JDK8u?

Regards,
Sergey Tsypanov
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sj.patch
Type: text/x-diff
Size: 15845 bytes
Desc: not available
URL: <https://mail.openjdk.java.net/pipermail/jdk8u-dev/attachments/20200508/28a40d14/sj-0001.patch>


More information about the jdk8u-dev mailing list