[PATCH] optimization opportunity regarding misuse of BufferedInputStream

Сергей Цыпанов sergei.tsypanov at yandex.ru
Thu Aug 27 20:09:28 UTC 2020


Hi,

while looking into java.util.jar.JarInputStream I've found a misuse of BufferedInputStream in checkManifest() method
where InputStream is wrapped into BIS and later read from with separately created byte[] buffer.

At runtime this means the data is copied from original IS into BIS and from there to byte[] resulting in redundant memory
allocation. The benchmark [1] demonstrates that for both scenarios (with and without verification) we can save memory
and reduce execution time by dropping BIS:

original

                                      Mode  Cnt       Score     Error   Units
read                                  avgt   50     230.301 ±   2.130   us/op
read:·gc.alloc.rate.norm              avgt   50  148929.020 ±  22.383    B/op
readNoVerify                          avgt   50     228.673 ±   0.556   us/op
readNoVerify:·gc.alloc.rate.norm      avgt   50  148133.555 ±   9.599    B/op

patched

                                      Mode  Cnt       Score     Error   Units
read                                  avgt   50     225.976 ±   0.543   us/op
read:·gc.alloc.rate.norm              avgt   50  140672.404 ±  20.732    B/op
readNoVerify                          avgt   50     229.563 ±   1.731   us/op
readNoVerify:·gc.alloc.rate.norm      avgt   50  139874.648 ±   7.054    B/op


Also InputStream.transferTo() can be called for the sake of code reuse.

Another snippets are located in MimeTable and in JmodFile:

- in MimeTable InputStream is passed into Properties.load() where they use
a buffer of exactly the same size (8192) as in BufferedInputStream. Benchmark [2]
demonstrates significant improvement when redundant bufferization is reduced:

                                               Mode  Cnt      Score     Error   Units
buffered                                       avgt   50    155.477 ±   6.370   us/op
buffered:·gc.alloc.rate.norm                   avgt   50  46263.355 ±   1.632    B/op
conventional                                   avgt   50    146.762 ±   3.481   us/op
conventional:·gc.alloc.rate.norm               avgt   50  38014.521 ±   7.152    B/op

- in JmodFile we read only the 4 first bytes but BufferedInputStream fills in the whole 
buffer (by default again 8192)

Unfortunately, I couldn't construct benchmark for JmodFile, however I'm
sure there'll be improvement either.

Patch is attached. It also includes some tiny clean-ups in mentioned classes, e.g.
dead code removal

With best regards,
Sergey Tsypanov


1.

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ReadManifestBenchmark {

  private final ClassLoader classLoader = getClass().getClassLoader();

  @Benchmark
  public Manifest read() throws IOException {
    return readManifest(true);
  }

  @Benchmark
  public Manifest readNoVerify() throws IOException {
    return readManifest(false);
  }

  private Manifest readManifest(boolean verify) throws IOException {
    try (
      InputStream resourceAsStream = classLoader.getResourceAsStream("jmh-core-1.23.jar");
      JarInputStream jarInputStream = new JarInputStream(resourceAsStream, verify)
    ) {
      return jarInputStream.getManifest();
    }
  }
}

2.

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class LoadPropertiesBenchmark {

  private final ClassLoader classLoader = getClass().getClassLoader();

  @Benchmark
  public Object conventional() throws IOException {
    try (InputStream is = getClass().getClassLoader().getResource("sun/net/www/content-types.properties").openStream()) {
      Properties p = new Properties();
      p.load(is);
      return p;
    }
  }

  @Benchmark
  public Object buffered() throws IOException {
    try (InputStream is = new BufferedInputStream(getClass().getClassLoader().getResource("sun/net/www/content-types.properties").openStream())) {
      Properties p = new Properties();
      p.load(is);
      return p;
    }
  }
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: buffered.patch
Type: text/x-diff
Size: 4110 bytes
Desc: not available
URL: <https://mail.openjdk.java.net/pipermail/core-libs-dev/attachments/20200827/0d338741/buffered.patch>


More information about the core-libs-dev mailing list