Skip to content
This repository has been archived by the owner on Aug 27, 2022. It is now read-only.

Commit

Permalink
8242848: Improve performance of InflaterOutputStream.write()
Browse files Browse the repository at this point in the history
Reviewed-by: stuefe, vtewari, redestad, lancea
  • Loading branch information
simonis committed Apr 23, 2020
1 parent 4f05f3f commit 2594f0b
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 20 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -236,16 +236,9 @@ public void write(byte[] b, int off, int len) throws IOException {

// Fill the decompressor buffer with output data
if (inf.needsInput()) {
int part;

if (len < 1) {
break;
}

part = (len < 512 ? len : 512);
inf.setInput(b, off, part);
off += part;
len -= part;
inf.setInput(b, off, len);
// Only use input buffer once.
len = 0;
}

// Decompress and write blocks of output data
Expand All @@ -256,13 +249,14 @@ public void write(byte[] b, int off, int len) throws IOException {
}
} while (n > 0);

// Check the decompressor
if (inf.finished()) {
break;
}
// Check for missing dictionary first
if (inf.needsDictionary()) {
throw new ZipException("ZLIB dictionary missing");
}
// Check the decompressor
if (inf.finished() || (len == 0)/* no more input */) {
break;
}
}
} catch (DataFormatException ex) {
// Improperly formatted compressed (ZIP) data
Expand Down
74 changes: 69 additions & 5 deletions test/jdk/java/util/zip/DeflateIn_InflateOut.java
Expand Up @@ -41,12 +41,29 @@ public class DeflateIn_InflateOut {
private static ByteArrayOutputStream baos;
private static InflaterOutputStream ios;

private static void reset() {
private static Inflater reset(byte[] dict) {
bais = new ByteArrayInputStream(data);
dis = new DeflaterInputStream(bais);
if (dict == null) {
dis = new DeflaterInputStream(bais);
} else {
Deflater def = new Deflater();
def.setDictionary(dict);
dis = new DeflaterInputStream(bais, def);
}

baos = new ByteArrayOutputStream();
ios = new InflaterOutputStream(baos);
if (dict == null) {
ios = new InflaterOutputStream(baos);
return null;
} else {
Inflater inf = new Inflater();
ios = new InflaterOutputStream(baos, inf);
return inf;
}
}

private static void reset() {
reset(null);
}

/** Check byte arrays read/write. */
Expand Down Expand Up @@ -214,6 +231,44 @@ private static void SkipBytes() throws Throwable {
check(numNotSkipped + numSkipBytes == numReadable);
}

/** Check "needsDictionary()". */
private static void NeedsDictionary() throws Throwable {
byte[] dict = {1, 2, 3, 4};
Adler32 adler32 = new Adler32();
adler32.update(dict);
long checksum = adler32.getValue();
byte[] buf = new byte[512];

Inflater inf = reset(dict);
check(dis.available() == 1);
boolean dictSet = false;
for (;;) {
int len = dis.read(buf, 0, buf.length);
if (len < 0) {
break;
} else {
try {
ios.write(buf, 0, len);
if (dictSet == false) {
check(false, "Must throw ZipException without dictionary");
return;
}
} catch (ZipException ze) {
check(dictSet == false, "Dictonary must be set only once");
check(checksum == inf.getAdler(), "Incorrect dictionary");
inf.setDictionary(dict);
// After setting the dictionary, we have to flush the
// InflaterOutputStream now in order to consume all the
// pending input data from the last, failed call to "write()".
ios.flush();
dictSet = true;
}
}
}
check(dis.available() == 0);
ios.close();
check(Arrays.equals(data, baos.toByteArray()));
}

public static void realMain(String[] args) throws Throwable {
new Random(new Date().getTime()).nextBytes(data);
Expand All @@ -227,15 +282,24 @@ public static void realMain(String[] args) throws Throwable {
ByteReadByteWrite();

SkipBytes();

NeedsDictionary();
}

//--------------------- Infrastructure ---------------------------
static volatile int passed = 0, failed = 0;
static void pass() {passed++;}
static void fail() {failed++; Thread.dumpStack();}
static void fail(String msg) {System.out.println(msg); fail();}
static void fail() { fail(null); }
static void fail(String msg) {
failed++;
if (msg != null) {
System.err.println(msg);
}
Thread.dumpStack();
}
static void unexpected(Throwable t) {failed++; t.printStackTrace();}
static void check(boolean cond) {if (cond) pass(); else fail();}
static void check(boolean cond, String msg) {if (cond) pass(); else fail(msg);}
static void equal(Object x, Object y) {
if (x == null ? y == null : x.equals(y)) pass();
else fail(x + " not equal to " + y);}
Expand Down
137 changes: 137 additions & 0 deletions test/micro/org/openjdk/bench/java/util/zip/Streams.java
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2020, Amazon and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package org.openjdk.bench.java.util.zip;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import java.util.zip.DeflaterOutputStream;

/**
* Test the average execution time of "InflaterOutputStream.write()" depending
* on the size of the internal "InflaterOutputStream" byte buffer and the size
* of the compressed data input buffer passed to "write()".
*
* The size of the compressed data input buffer is controlled by the "size"
* parameter which runs from "512" to "65536".
*
* The size of the internal byte buffer is a multiple of "size" controlled by
* the "scale" paramter which runs from "1" to "8".
*
* For peak perfomance the internal buffer should be big enough to hold all
* the data decompressed from the input buffer. This of course depends on
* the compression rate of the input data. E.g. if the compression rate of
* the compressed input data is 4 (i.e. the original input data was compressed
* to 1/4 of its original size) the internal buffer should be four times bigger
* than the size of the compressed data buffer passed to "write()" because in
* that case one single call to the native zlib "inflate()" method is sufficent
* to decompress all data and store it in the output buffer from where it can
* be written to the output stream with one single call to the output streams
* "write()" method.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class Streams {

private FileInputStream in;
private FileOutputStream out;
@Param({"512", "1024", "2048", "4096", "8192", "16384", "32768", "65536"})
private int size;
@Param({"1", "2", "4", "8"})
private int scale;
private byte[] buf;

private static byte[] data = new byte[1024 * 1024];

@Setup(Level.Trial)
public void beforeRun() throws IOException {
// The reason for this whole dance is to programmatically create a one
// megabyte file which can be compressed by factor ~6. This will give
// us good results for the various scale factors (i.e. the relation
// between the deflated input buffer and the inflated output buffer).
// We achieve the desired compression factor by creating a 64 byte
// array of random data and than fill the final 1mb file with random
// 8-byte substrings of these 64 random bytes. This vaguely mimics
// a language with 8 character words over a set of 64 different characters.
final int characters = 64;
final int wordLength = 8;
buf = new byte[characters];
Random r = new Random(123456789);
r.nextBytes(buf);
for (int i = 0; i < data.length / wordLength; i++) {
System.arraycopy(buf, r.nextInt(characters - wordLength), data, i * wordLength, wordLength);
}
ByteArrayInputStream bais = new ByteArrayInputStream(data);

File deflated = File.createTempFile("inflaterOutputStreamWrite", ".deflated");
deflated.deleteOnExit();
FileOutputStream fout = new FileOutputStream(deflated);
DeflaterOutputStream defout = new DeflaterOutputStream(fout);
bais.transferTo(defout);
// We need to close the DeflaterOutputStream in order to flush all the
// compressed data in the Deflater and the underlying FileOutputStream.
defout.close();
in = new FileInputStream(deflated);
File inflated = File.createTempFile("inflaterOutputStreamWrite", ".inflated");
inflated.deleteOnExit();
out = new FileOutputStream(inflated);
}

@Setup(Level.Iteration)
public void beforeIteration() throws IOException {
in.getChannel().position(0);
out.getChannel().position(0);
buf = new byte[size];
}

@Benchmark
public void inflaterOutputStreamWrite() throws IOException {
in.getChannel().position(0);
out.getChannel().position(0);
InflaterOutputStream inflate = new InflaterOutputStream(out, new Inflater(), scale * size);
int len;
// buf.length == size
while ((len = in.read(buf)) != -1) {
inflate.write(buf, 0, len);
}
inflate.finish();
}
}

0 comments on commit 2594f0b

Please sign in to comment.