6751338: ZIP inflater/deflater performance
authorsherman
Mon, 04 Apr 2011 11:30:55 -0700
changeset 9023 6175922846a4
parent 9021 5cf29386a520
child 9024 e996d2d7c4d1
6751338: ZIP inflater/deflater performance Summary: To use GetPrimitiveArrayCritical for bye array access Reviewed-by: bristor, alanb
jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java
jdk/src/share/native/java/util/zip/Deflater.c
jdk/src/share/native/java/util/zip/Inflater.c
jdk/test/java/util/zip/FlaterCriticalArray.java
jdk/test/java/util/zip/InflaterBufferSize.java
--- a/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java	Thu Mar 31 22:07:32 2011 -0700
+++ b/jdk/src/share/classes/java/util/zip/DeflaterOutputStream.java	Mon Apr 04 11:30:55 2011 -0700
@@ -206,14 +206,9 @@
             return;
         }
         if (!def.finished()) {
-            // Deflate no more than stride bytes at a time.  This avoids
-            // excess copying in deflateBytes (see Deflater.c)
-            int stride = buf.length;
-            for (int i = 0; i < len; i+= stride) {
-                def.setInput(b, off + i, Math.min(stride, len - i));
-                while (!def.needsInput()) {
-                    deflate();
-                }
+            def.setInput(b, off, len);
+            while (!def.needsInput()) {
+                deflate();
             }
         }
     }
--- a/jdk/src/share/native/java/util/zip/Deflater.c	Thu Mar 31 22:07:32 2011 -0700
+++ b/jdk/src/share/native/java/util/zip/Deflater.c	Mon Apr 04 11:30:55 2011 -0700
@@ -129,34 +129,28 @@
     if ((*env)->GetBooleanField(env, this, setParamsID)) {
         int level = (*env)->GetIntField(env, this, levelID);
         int strategy = (*env)->GetIntField(env, this, strategyID);
-
-        in_buf = (jbyte *) malloc(this_len);
-        if (in_buf == 0) {
+        in_buf = (*env)->GetPrimitiveArrayCritical(env, this_buf, 0);
+        if (in_buf == NULL) {
             // Throw OOME only when length is not zero
             if (this_len != 0)
                 JNU_ThrowOutOfMemoryError(env, 0);
             return 0;
         }
-        (*env)->GetByteArrayRegion(env, this_buf, this_off, this_len, in_buf);
-        out_buf = (jbyte *) malloc(len);
-        if (out_buf == 0) {
-            free(in_buf);
+        out_buf = (*env)->GetPrimitiveArrayCritical(env, b, 0);
+        if (out_buf == NULL) {
+            (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
             if (len != 0)
                 JNU_ThrowOutOfMemoryError(env, 0);
             return 0;
         }
 
-        strm->next_in = (Bytef *) in_buf;
-        strm->next_out = (Bytef *) out_buf;
+        strm->next_in = (Bytef *) (in_buf + this_off);
+        strm->next_out = (Bytef *) (out_buf + off);
         strm->avail_in = this_len;
         strm->avail_out = len;
         res = deflateParams(strm, level, strategy);
-
-        if (res == Z_OK) {
-            (*env)->SetByteArrayRegion(env, b, off, len - strm->avail_out, out_buf);
-        }
-        free(out_buf);
-        free(in_buf);
+        (*env)->ReleasePrimitiveArrayCritical(env, b, out_buf, 0);
+        (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
 
         switch (res) {
         case Z_OK:
@@ -174,33 +168,28 @@
         }
     } else {
         jboolean finish = (*env)->GetBooleanField(env, this, finishID);
-        in_buf = (jbyte *) malloc(this_len);
-        if (in_buf == 0) {
+        in_buf = (*env)->GetPrimitiveArrayCritical(env, this_buf, 0);
+        if (in_buf == NULL) {
             if (this_len != 0)
                 JNU_ThrowOutOfMemoryError(env, 0);
             return 0;
         }
-        (*env)->GetByteArrayRegion(env, this_buf, this_off, this_len, in_buf);
-
-        out_buf = (jbyte *) malloc(len);
-        if (out_buf == 0) {
-            free(in_buf);
+        out_buf = (*env)->GetPrimitiveArrayCritical(env, b, 0);
+        if (out_buf == NULL) {
+            (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
             if (len != 0)
                 JNU_ThrowOutOfMemoryError(env, 0);
+
             return 0;
         }
 
-        strm->next_in = (Bytef *) in_buf;
-        strm->next_out = (Bytef *) out_buf;
+        strm->next_in = (Bytef *) (in_buf + this_off);
+        strm->next_out = (Bytef *) (out_buf + off);
         strm->avail_in = this_len;
         strm->avail_out = len;
         res = deflate(strm, finish ? Z_FINISH : flush);
-
-        if (res == Z_STREAM_END || res == Z_OK) {
-            (*env)->SetByteArrayRegion(env, b, off, len - strm->avail_out, out_buf);
-        }
-        free(out_buf);
-        free(in_buf);
+        (*env)->ReleasePrimitiveArrayCritical(env, b, out_buf, 0);
+        (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
 
         switch (res) {
         case Z_STREAM_END:
--- a/jdk/src/share/native/java/util/zip/Inflater.c	Thu Mar 31 22:07:32 2011 -0700
+++ b/jdk/src/share/native/java/util/zip/Inflater.c	Mon Apr 04 11:30:55 2011 -0700
@@ -38,8 +38,6 @@
 #include "zlib.h"
 #include "java_util_zip_Inflater.h"
 
-#define MIN2(x, y)  ((x) < (y) ? (x) : (y))
-
 #define ThrowDataFormatException(env, msg) \
         JNU_ThrowByName(env, "java/util/zip/DataFormatException", msg)
 
@@ -111,71 +109,50 @@
                                          jarray b, jint off, jint len)
 {
     z_stream *strm = jlong_to_ptr(addr);
-
     jarray this_buf = (jarray)(*env)->GetObjectField(env, this, bufID);
     jint this_off = (*env)->GetIntField(env, this, offID);
     jint this_len = (*env)->GetIntField(env, this, lenID);
+
     jbyte *in_buf;
     jbyte *out_buf;
     int ret;
-    /*
-     * Avoid excess copying.
-     *   zlib stream usually has a few bytes of overhead for header info
-     *   (depends on the underlying data)
-     *
-     *   (a) 5 bytes per 16KB
-     *   (b) 6 bytes for entire stream
-     *   (c) 4 bytes for gzip header
-     *   (d) 2 bytes for crc
-     *
-     * Use 20 bytes as the "safe cutoff" number.
-     */
-    jint in_len = MIN2(this_len, len + 20);
-    jint consumed;
 
-    in_buf = (jbyte *) malloc(in_len);
-    if (in_buf == 0) {
-        if (in_len != 0)
+    in_buf  = (*env)->GetPrimitiveArrayCritical(env, this_buf, 0);
+    if (in_buf == NULL) {
+        if (this_len != 0)
             JNU_ThrowOutOfMemoryError(env, 0);
         return 0;
     }
-    (*env)->GetByteArrayRegion(env, this_buf, this_off, in_len, in_buf);
-
-    out_buf = (jbyte *) malloc(len);
-    if (out_buf == 0) {
-        free(in_buf);
+    out_buf = (*env)->GetPrimitiveArrayCritical(env, b, 0);
+    if (out_buf == NULL) {
+        (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
         if (len != 0)
             JNU_ThrowOutOfMemoryError(env, 0);
         return 0;
     }
-
-    strm->next_in  = (Bytef *) in_buf;
-    strm->next_out = (Bytef *) out_buf;
-    strm->avail_in  = in_len;
+    strm->next_in  = (Bytef *) (in_buf + this_off);
+    strm->next_out = (Bytef *) (out_buf + off);
+    strm->avail_in  = this_len;
     strm->avail_out = len;
     ret = inflate(strm, Z_PARTIAL_FLUSH);
-
-    if (ret == Z_STREAM_END || ret == Z_OK) {
-        (*env)->SetByteArrayRegion(env, b, off, len - strm->avail_out, out_buf);
-    }
-    free(out_buf);
-    free(in_buf);
+    (*env)->ReleasePrimitiveArrayCritical(env, b, out_buf, 0);
+    (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
 
     switch (ret) {
     case Z_STREAM_END:
         (*env)->SetBooleanField(env, this, finishedID, JNI_TRUE);
         /* fall through */
     case Z_OK:
-        consumed = in_len - strm->avail_in;
-        (*env)->SetIntField(env, this, offID, this_off + consumed);
-        (*env)->SetIntField(env, this, lenID, this_len - consumed);
+        this_off += this_len - strm->avail_in;
+        (*env)->SetIntField(env, this, offID, this_off);
+        (*env)->SetIntField(env, this, lenID, strm->avail_in);
         return len - strm->avail_out;
     case Z_NEED_DICT:
         (*env)->SetBooleanField(env, this, needDictID, JNI_TRUE);
         /* Might have consumed some input here! */
-        consumed = in_len - strm->avail_in;
-        (*env)->SetIntField(env, this, offID, this_off + consumed);
-        (*env)->SetIntField(env, this, lenID, this_len - consumed);
+        this_off += this_len - strm->avail_in;
+        (*env)->SetIntField(env, this, offID, this_off);
+        (*env)->SetIntField(env, this, lenID, strm->avail_in);
         return 0;
     case Z_BUF_ERROR:
         return 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/zip/FlaterCriticalArray.java	Mon Apr 04 11:30:55 2011 -0700
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2011, 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
+ * 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.
+ */
+
+/**
+ * ZIP inflater/deflater performance.
+ */
+
+/*
+ * Run this test on JDK 6 and on later
+ * JDKs to compare results:
+ *     java -server -Xms1024M -Xmx1024M -Ddebug=true FlaterCriticalArray
+ *
+ * The performance issues can be readily seen on JDK 6, and so this code is
+ * written to compile on that platform: it does *not* use any JDK 7 - specific
+ * features.
+ */
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+import java.util.zip.*;
+
+public class FlaterCriticalArray {
+    // If true, print information about performance
+    private static final boolean debug = System.getProperty("debug") != null;
+
+    private static void debug(String s) {
+        if (debug) System.out.println(s);
+    }
+
+    private static void debug(String name, String inOut, long time, int length) {
+        debug(name + ": Duration of " + inOut + "(in ms): " + time);
+        debug(name + ": " + inOut + "d data length: " + length + " bytes");
+    }
+
+    private static byte[] grow(byte[] a, int capacity) {
+        while (a.length < capacity) {
+            byte[] a2 = new byte[a.length * 2];
+            System.arraycopy(a, 0, a2, 0, a.length);
+            a = a2;
+        }
+        return a;
+    }
+
+    private static byte[] trim(byte[] a, int length) {
+        byte[] res = new byte[length];
+        System.arraycopy(a, 0, res, 0, length);
+        return res;
+    }
+
+    /*
+     * Base class for individual test cases
+     */
+    abstract static private class TestCase {
+        protected String name;  // For information in debug messages
+        protected byte data[];  // Data to be deflated and subsequently inflated
+        protected int level;    // Compression level for deflater
+
+        protected TestCase(String name, byte data[]) {
+            this(name, data, -1);
+        }
+
+        protected TestCase(String name, byte data[], int level) {
+            this.name = name;
+            this.data = data;
+            this.level = level;
+        }
+
+        public void runTest() throws Throwable {
+            long time0, time1;
+            byte deflated[], inflated[];
+
+            debug("");
+
+            time0 = System.currentTimeMillis();
+            deflated = deflate(data, level);
+            time1 = System.currentTimeMillis();
+            inform("Deflate", time1 - time0, deflated.length);
+
+            time0 = System.currentTimeMillis();
+            inflated = inflate(deflated);
+            time1 = System.currentTimeMillis();
+            inform("Inflate", time1 - time0, inflated.length);
+
+            check(Arrays.equals(data, inflated),
+                  name + ": Inflated and deflated arrays do not match");
+        }
+
+        private void inform(String inOut, long duration, int length) {
+            debug(name, inOut, duration, length);
+        }
+
+        abstract protected byte[] deflate(byte data[], int level) throws Throwable;
+
+        abstract protected byte[] inflate(byte deflated[]) throws Throwable;
+    }
+
+    /*
+     * Following are  the individual test cases
+     */
+
+    private static class StrideTest extends TestCase {
+        static final int STRIDE = 1024;
+
+        public StrideTest(byte data[], int level) {
+            super("STRIDE", data, level);
+        }
+
+        protected byte[] deflate(byte in[], int level) throws Throwable {
+            final int len = in.length;
+            final Deflater deflater = new Deflater(level);
+            final byte[] smallBuffer = new byte[32];
+            byte[] flated = new byte[32];
+            int count = 0;
+            for (int i = 0; i<len; i+= STRIDE) {
+                deflater.setInput(in, i, Math.min(STRIDE, len-i));
+                while (!deflater.needsInput()) {
+                    int n = deflater.deflate(smallBuffer);
+                    flated = grow(flated, count + n);
+                    System.arraycopy(smallBuffer, 0, flated, count, n);
+                    count += n;
+                }
+            }
+            deflater.finish();
+            int n;
+            do {
+                n = deflater.deflate(smallBuffer);
+                flated = grow(flated, count + n);
+                System.arraycopy(smallBuffer, 0, flated, count, n);
+                count += n;
+            } while (n > 0);
+            return trim(flated, count);
+        }
+
+        protected byte[] inflate(byte in[]) throws Throwable {
+            final int len = in.length;
+            final Inflater inflater = new Inflater();
+            final byte[] smallBuffer = new byte[3200];
+
+            byte[] flated = new byte[32];
+            int count = 0;
+
+            for (int i = 0; i<len; i+= STRIDE) {
+                inflater.setInput(in, i, Math.min(STRIDE, len-i));
+                while (!inflater.needsInput()) {
+                    int n;
+                    while ((n = inflater.inflate(smallBuffer)) > 0) {
+                        flated = grow(flated, count + n);
+                        System.arraycopy(smallBuffer, 0, flated, count, n);
+                        count += n;
+                    }
+                }
+            }
+            return trim(flated, count);
+        }
+    }
+
+    private static class NoStrideTest extends TestCase {
+        public NoStrideTest(byte data[], int level) {
+            super("NO STRIDE", data, level);
+        }
+
+        public byte[] deflate(byte in[], int level) throws Throwable {
+            final Deflater flater = new Deflater(level);
+            flater.setInput(in);
+            flater.finish();
+            final byte[] smallBuffer = new byte[32];
+            byte[] flated = new byte[32];
+            int count = 0;
+            int n;
+            while ((n = flater.deflate(smallBuffer)) > 0) {
+                flated = grow(flated, count + n);
+                System.arraycopy(smallBuffer, 0, flated, count, n);
+                count += n;
+            }
+            return trim(flated, count);
+        }
+
+        public byte[] inflate(byte in[]) throws Throwable {
+            final Inflater flater = new Inflater();
+            flater.setInput(in);
+            final byte[] smallBuffer = new byte[32];
+            byte[] flated = new byte[32];
+            int count = 0;
+            int n;
+            while ((n = flater.inflate(smallBuffer)) > 0) {
+                flated = grow(flated, count + n);
+                System.arraycopy(smallBuffer, 0, flated, count, n);
+                count += n;
+            }
+            return trim(flated, count);
+        }
+    }
+
+    /**
+     * Check Deflater{In,Out}putStream by way of GZIP{In,Out}putStream
+     */
+    private static class GZIPTest extends TestCase {
+        public GZIPTest(byte data[]) {
+            super("GZIP", data);
+        }
+
+        public byte[] deflate(byte data[], int ignored) throws Throwable {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            OutputStream gzos = new GZIPOutputStream(baos);
+            gzos.write(data, 0, data.length);
+            gzos.close();
+            return baos.toByteArray();
+        }
+
+        public byte[] inflate(byte deflated[]) throws Throwable {
+            InputStream bais = new ByteArrayInputStream(deflated);
+            GZIPInputStream gzis = new GZIPInputStream(bais);
+            byte[] inflated = new byte[data.length];
+            int numRead = 0;
+            int count = 0;
+            while ((numRead = gzis.read(inflated, count, data.length - count)) > 0) {
+                count += numRead;
+            }
+            check(count == data.length, name + ": Read " + count + "; expected " + data.length);
+            return inflated;
+        }
+    }
+
+    public static void realMain(String[] args) throws Throwable {
+        byte data[];
+        int level = -1;
+        if (args.length > 0) {
+          level = Integer.parseInt(args[0]);
+        }
+        debug("Using level " + level);
+
+        if (args.length > 1) {
+            FileInputStream fis = new FileInputStream(args[1]);
+            int len = fis.available();
+            data = new byte[len];
+            check(fis.read(data, 0, len) == len, "Did not read complete file");
+            debug("Original data from " + args[1]);
+            fis.close();
+        } else {
+            ByteBuffer bb = ByteBuffer.allocate(8);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            for (int i = 0; i < 1024 * 64; i++) { // data length
+                bb.putDouble(0, Math.random());
+                baos.write(bb.array(), 0, 8);
+            }
+            data = baos.toByteArray();
+            debug("Original data from random byte array");
+        }
+        debug("Original data length: " + data.length + " bytes");
+
+        new StrideTest(data, level).runTest();
+        new NoStrideTest(data, level).runTest();
+        new GZIPTest(data).runTest();
+    }
+
+    //--------------------- Infrastructure ---------------------------
+    static volatile int passed = 0, failed = 0;
+    static void pass() {passed++;}
+    static void pass(String msg) {System.out.println(msg); passed++;}
+    static void fail() {failed++; Thread.dumpStack();}
+    static void fail(String msg) {System.out.println(msg); fail();}
+    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
+    static void unexpected(Throwable t, String msg) {
+        System.out.println(msg); failed++; t.printStackTrace();}
+    static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
+    static boolean check(boolean cond, String msg) {if (cond) pass(); else fail(msg); return cond;}
+    static void equal(Object x, Object y) {
+        if (x == null ? y == null : x.equals(y)) pass();
+        else fail(x + " not equal to " + y);}
+    public static void main(String[] args) throws Throwable {
+        try {realMain(args);} catch (Throwable t) {unexpected(t);}
+        System.out.println("\nPassed = " + passed + " failed = " + failed);
+        if (failed > 0) throw new AssertionError("Some tests failed");}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/zip/InflaterBufferSize.java	Mon Apr 04 11:30:55 2011 -0700
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2011, 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
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 6571338
+ * @summary Inflater should not require a buffer to the inflate() methods
+ * larger than 1 byte.
+ */
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+import java.util.zip.*;
+
+public class InflaterBufferSize {
+    private static final int DATA_LEN = 1024 *64;
+    private static byte[] data;
+
+    // If true, print extra info.
+    private static final boolean debug = System.getProperty("debug") != null;
+
+    private static void debug(String s) {
+        if (debug) System.out.println(s);
+    }
+
+    private static void createData() {
+        ByteBuffer bb = ByteBuffer.allocate(8);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        for (int i = 0; i < DATA_LEN; i++) {
+            bb.putDouble(0, Math.random());
+            baos.write(bb.array(), 0, 8);
+        }
+        data = baos.toByteArray();
+    }
+
+    private static byte[] grow(byte[] a, int capacity) {
+        while (a.length < capacity) {
+            byte[] a2 = new byte[a.length * 2];
+            System.arraycopy(a, 0, a2, 0, a.length);
+            a = a2;
+        }
+        return a;
+    }
+
+    private static byte[] trim(byte[] a, int length) {
+        byte[] res = new byte[length];
+        System.arraycopy(a, 0, res, 0, length);
+        return res;
+    }
+
+    private static byte[] deflate(byte[] in, int level) throws Throwable {
+        final Deflater flater = new Deflater(level);
+        flater.setInput(in);
+        flater.finish();
+        final byte[] smallBuffer = new byte[32];
+        byte[] flated = new byte[32];
+        int count = 0;
+        int n;
+        while ((n = flater.deflate(smallBuffer)) > 0) {
+            flated = grow(flated, count + n);
+            System.arraycopy(smallBuffer, 0, flated, count, n);
+            count += n;
+        }
+        return trim(flated, count);
+    }
+
+    private static byte[] inflate(byte[] in) throws Throwable {
+        final Inflater flater = new Inflater();
+        flater.setInput(in);
+        // This is the buffer of interest.  It should be possible to use any
+        // non-zero size.
+        final byte[] smallBuffer = new byte[1];
+        byte[] flated = new byte[32];
+        int count = 0;
+        int n;
+        while ((n = flater.inflate(smallBuffer)) > 0) {
+            flated = grow(flated, count + n);
+            System.arraycopy(smallBuffer, 0, flated, count, n);
+            count += n;
+        }
+        return trim(flated, count);
+    }
+
+    public static void realMain(String[] args) throws Throwable {
+        byte deflated[], inflated[];
+
+        int level = -1;
+        if (args.length > 0) {
+          level = Integer.parseInt(args[0]);
+        }
+        debug("Using level " + level);
+
+        if (args.length > 1) {
+            FileInputStream fis = new FileInputStream(args[1]);
+            int len = fis.available();
+            data = new byte[len];
+            check(fis.read(data, 0, len) == len, "Did not read complete file");
+            debug("Original data from " + args[1]);
+            fis.close();
+        } else {
+            createData();
+            debug("Original data from random byte array");
+        }
+        debug("Original data length: " + data.length + " bytes");
+
+        debug("");
+        deflated = deflate(data, level);
+        debug("Deflated data length: " + deflated.length + " bytes");
+
+        inflated = inflate(deflated);
+        debug("Inflated data length: "+ inflated.length + " bytes" );
+
+        if (!check(Arrays.equals(data, inflated),
+                   "Inflated and deflated arrays do not match")) {
+            OutputStream os = new BufferedOutputStream(new FileOutputStream("deflated.zip"));
+            try {
+                os.write(deflated);
+            } finally {
+                os.close();
+            }
+        }
+    }
+
+    //--------------------- Infrastructure ---------------------------
+    static volatile int passed = 0, failed = 0;
+    static void pass() {passed++;}
+    static void pass(String msg) {System.out.println(msg); passed++;}
+    static void fail() {failed++; Thread.dumpStack();}
+    static void fail(String msg) {System.out.println(msg); fail();}
+    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
+    static void unexpected(Throwable t, String msg) {
+        System.out.println(msg); failed++; t.printStackTrace();}
+    static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
+    static boolean check(boolean cond, String msg) {if (cond) pass(); else fail(msg); return cond;}
+    static void equal(Object x, Object y) {
+        if (x == null ? y == null : x.equals(y)) pass();
+        else fail(x + " not equal to " + y);}
+    public static void main(String[] args) throws Throwable {
+        try {realMain(args);} catch (Throwable t) {unexpected(t);}
+        System.out.println("\nPassed = " + passed + " failed = " + failed);
+        if (failed > 0) throw new AssertionError("Some tests failed");}
+}