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;
}
}
}
diff --git a/src/java.base/share/classes/java/util/jar/JarInputStream.java b/src/java.base/share/classes/java/util/jar/JarInputStream.java
--- a/src/java.base/share/classes/java/util/jar/JarInputStream.java
+++ b/src/java.base/share/classes/java/util/jar/JarInputStream.java
@@ -90,7 +90,7 @@
{
if (e != null && JarFile.MANIFEST_NAME.equalsIgnoreCase(e.getName())) {
man = new Manifest();
- byte bytes[] = getBytes(new BufferedInputStream(this));
+ byte[] bytes = getBytes(this);
man.read(new ByteArrayInputStream(bytes));
closeEntry();
if (doVerify) {
@@ -105,12 +105,8 @@
private byte[] getBytes(InputStream is)
throws IOException
{
- byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
- int n;
- while ((n = is.read(buffer, 0, buffer.length)) != -1) {
- baos.write(buffer, 0, n);
- }
+ is.transferTo(baos);
return baos.toByteArray();
}
@@ -153,7 +149,7 @@
// At this point, we might have parsed all the meta-inf
// entries and have nothing to verify. If we have
// nothing to verify, get rid of the JarVerifier object.
- if (jv.nothingToVerify() == true) {
+ if (jv.nothingToVerify()) {
jv = null;
mev = null;
} else {
diff --git a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
--- a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
+++ b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
@@ -25,7 +25,6 @@
package jdk.internal.jmod;
-import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -52,11 +51,10 @@
};
public static void checkMagic(Path file) throws IOException {
- try (InputStream in = Files.newInputStream(file);
- BufferedInputStream bis = new BufferedInputStream(in)) {
+ try (InputStream in = Files.newInputStream(file)) {
// validate the header
byte[] magic = new byte[4];
- bis.read(magic);
+ in.read(magic);
if (magic[0] != JMOD_MAGIC_NUMBER[0] ||
magic[1] != JMOD_MAGIC_NUMBER[1]) {
throw new IOException("Invalid JMOD file: " + file.toString());
@@ -72,7 +70,7 @@
/**
* JMOD sections
*/
- public static enum Section {
+ public enum Section {
CLASSES("classes"),
CONFIG("conf"),
HEADER_FILES("include"),
diff --git a/src/java.base/share/classes/sun/net/www/MimeTable.java b/src/java.base/share/classes/sun/net/www/MimeTable.java
--- a/src/java.base/share/classes/sun/net/www/MimeTable.java
+++ b/src/java.base/share/classes/sun/net/www/MimeTable.java
@@ -67,7 +67,6 @@
private static final String filePreamble = "sun.net.www MIME content-types table";
- private static final String fileMagic = "#" + filePreamble;
MimeTable() {
load();
@@ -242,7 +241,7 @@
throw new InternalError("default mime table not found");
}
- try (BufferedInputStream bin = new BufferedInputStream(in)) {
+ try (InputStream bin = in) {
entries.load(bin);
} catch (IOException e) {
System.err.println("Warning: " + e.getMessage());
@@ -347,17 +346,6 @@
// else illegal name exception
}
- String[] getExtensions(String list) {
- StringTokenizer tokenizer = new StringTokenizer(list, ",");
- int n = tokenizer.countTokens();
- String[] extensions = new String[n];
- for (int i = 0; i < n; i++) {
- extensions[i] = tokenizer.nextToken();
- }
-
- return extensions;
- }
-
int getActionCode(String action) {
for (int i = 0; i < MimeEntry.actionKeywords.length; i++) {
if (action.equalsIgnoreCase(MimeEntry.actionKeywords[i])) {