-
Bug
-
Resolution: Fixed
-
P3
-
6
-
b78
-
generic
-
generic
A DESCRIPTION OF THE FIX :
I noticed that java.security.MessageDigestSpi creates a lot of unnecessary garbage by always creating a new byte[] in engineUpdate(ByteBuffer input). By re-using a byte[] this problem can be fixed. A nice side effect is that by reducing the stress on the garbage collector MD5 becomes 2% faster. Here is the diff against jdk-6_0-ea-src-b46-jrl-04_aug_2005:
--- MessageDigestSpi.orig 2005-08-11 16:25:14.390085369 +0200
+++ MessageDigestSpi.java 2005-08-11 16:48:43.830953629 +0200
@@ -7,14 +7,6 @@
package java.security;
-import java.util.*;
-import java.lang.*;
-import java.io.IOException;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.io.InputStream;
-import java.io.ByteArrayInputStream;
-
import java.nio.ByteBuffer;
import sun.security.jca.JCAUtil;
@@ -33,14 +25,18 @@
* <p> Implementations are free to implement the Cloneable interface.
*
* @author Benjamin Renaud
+ * @author 20050811 Ronny Standtke <###@###.###>: garbage minimization, fixed imports
*
- * @version 1.15, 12/19/03
+ * @version 1.16, 20050811
*
* @see MessageDigest
*/
public abstract class MessageDigestSpi {
+ // for re-use in engineUpdate(ByteBuffer input)
+ private byte[] tempArray = new byte[0];
+
/**
* Returns the digest length in bytes.
*
@@ -102,13 +98,18 @@
engineUpdate(b, ofs + pos, lim - pos);
input.position(lim);
} else {
- int len = input.remaining();
- byte[] b = new byte[JCAUtil.getTempArraySize(len)];
- while (len > 0) {
- int chunk = Math.min(len, b.length);
- input.get(b, 0, chunk);
- engineUpdate(b, 0, chunk);
- len -= chunk;
+ int remaining = input.remaining();
+
+ // only create new tempArray if the old one is too small
+ int tempArraySize = JCAUtil.getTempArraySize(remaining);
+ if (tempArraySize > tempArray.length) {
+ tempArray = new byte[tempArraySize];
+ }
+ while (remaining > 0) {
+ int chunk = Math.min(remaining, tempArray.length);
+ input.get(tempArray, 0, chunk);
+ engineUpdate(tempArray, 0, chunk);
+ remaining -= chunk;
}
}
}
JUnit TESTCASE :
Sorry, no JUnit test but a simple test app to verify the speed gains:
/*
* MD5Speed.java
*
* Created on 20. Juli 2005, 15:52
*
*/
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.text.NumberFormat;
import java.util.*;
/**
*
* @author ###@###.###
*/
public class MD5Speed {
private static final int testSeconds = 10;
private static MessageDigest messageDigest;
private static boolean running;
private static long startTime;
private static long rounds;
private static Timer timer;
private static class CancelTestTimerTask extends TimerTask {
public void run() {
stopTest();
}
}
private static NumberFormat numberFormat;
public MD5Speed() {
// info
System.out.println("Info: running all tests for " + testSeconds + " seconds");
// init
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
timer = new Timer();
numberFormat = NumberFormat.getIntegerInstance();
// get the hotspot compiler into action...
byte[] input = new byte[(int)1500];
ByteBuffer buffer = ByteBuffer.allocate(1500);
for (int i = 0; i < 11000; i ++) {
messageDigest.update(input);
messageDigest.update(buffer);
messageDigest.digest();
}
System.out.println("== Array Tests ==");
arrayTest( 16);
arrayTest( 64);
arrayTest( 256);
arrayTest(1024);
arrayTest(8192);
System.out.println("== Buffer Tests ==");
bufferTest( 16);
bufferTest( 64);
bufferTest( 256);
bufferTest(1024);
bufferTest(8192);
System.exit(0);
}
private static void stopTest() {
running = false;
}
private static void startTest(long blockSize) {
System.out.print(blockSize + " Byte ... ");
startTime = System.currentTimeMillis();
rounds = 0;
CancelTestTimerTask cancelTestTimerTask = new CancelTestTimerTask();
timer.schedule(cancelTestTimerTask, 1000 * testSeconds);
running = true;
}
private static void testResult(long blockSize) {
long stopTime = System.currentTimeMillis();
long time = stopTime - startTime;
long speed = ((long)1000 * blockSize * rounds) / time;
messageDigest.reset();
System.out.println(numberFormat.format(rounds) + " rounds = " + numberFormat.format(speed) + " Bytes/s");
}
private static void arrayTest(long blockSize) {
byte[] input = new byte[(int)blockSize];
startTest(blockSize);
while (running) {
messageDigest.update(input);
messageDigest.digest();
rounds++;
}
testResult(blockSize);
}
private static void bufferTest(int blockSize) {
ByteBuffer buffer = ByteBuffer.allocate(blockSize);
startTest(blockSize);
while (running) {
messageDigest.update(buffer);
messageDigest.digest();
buffer.clear();
rounds++;
}
testResult(blockSize);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
new MD5Speed();
}
}
I noticed that java.security.MessageDigestSpi creates a lot of unnecessary garbage by always creating a new byte[] in engineUpdate(ByteBuffer input). By re-using a byte[] this problem can be fixed. A nice side effect is that by reducing the stress on the garbage collector MD5 becomes 2% faster. Here is the diff against jdk-6_0-ea-src-b46-jrl-04_aug_2005:
--- MessageDigestSpi.orig 2005-08-11 16:25:14.390085369 +0200
+++ MessageDigestSpi.java 2005-08-11 16:48:43.830953629 +0200
@@ -7,14 +7,6 @@
package java.security;
-import java.util.*;
-import java.lang.*;
-import java.io.IOException;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.io.InputStream;
-import java.io.ByteArrayInputStream;
-
import java.nio.ByteBuffer;
import sun.security.jca.JCAUtil;
@@ -33,14 +25,18 @@
* <p> Implementations are free to implement the Cloneable interface.
*
* @author Benjamin Renaud
+ * @author 20050811 Ronny Standtke <###@###.###>: garbage minimization, fixed imports
*
- * @version 1.15, 12/19/03
+ * @version 1.16, 20050811
*
* @see MessageDigest
*/
public abstract class MessageDigestSpi {
+ // for re-use in engineUpdate(ByteBuffer input)
+ private byte[] tempArray = new byte[0];
+
/**
* Returns the digest length in bytes.
*
@@ -102,13 +98,18 @@
engineUpdate(b, ofs + pos, lim - pos);
input.position(lim);
} else {
- int len = input.remaining();
- byte[] b = new byte[JCAUtil.getTempArraySize(len)];
- while (len > 0) {
- int chunk = Math.min(len, b.length);
- input.get(b, 0, chunk);
- engineUpdate(b, 0, chunk);
- len -= chunk;
+ int remaining = input.remaining();
+
+ // only create new tempArray if the old one is too small
+ int tempArraySize = JCAUtil.getTempArraySize(remaining);
+ if (tempArraySize > tempArray.length) {
+ tempArray = new byte[tempArraySize];
+ }
+ while (remaining > 0) {
+ int chunk = Math.min(remaining, tempArray.length);
+ input.get(tempArray, 0, chunk);
+ engineUpdate(tempArray, 0, chunk);
+ remaining -= chunk;
}
}
}
JUnit TESTCASE :
Sorry, no JUnit test but a simple test app to verify the speed gains:
/*
* MD5Speed.java
*
* Created on 20. Juli 2005, 15:52
*
*/
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.text.NumberFormat;
import java.util.*;
/**
*
* @author ###@###.###
*/
public class MD5Speed {
private static final int testSeconds = 10;
private static MessageDigest messageDigest;
private static boolean running;
private static long startTime;
private static long rounds;
private static Timer timer;
private static class CancelTestTimerTask extends TimerTask {
public void run() {
stopTest();
}
}
private static NumberFormat numberFormat;
public MD5Speed() {
// info
System.out.println("Info: running all tests for " + testSeconds + " seconds");
// init
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
timer = new Timer();
numberFormat = NumberFormat.getIntegerInstance();
// get the hotspot compiler into action...
byte[] input = new byte[(int)1500];
ByteBuffer buffer = ByteBuffer.allocate(1500);
for (int i = 0; i < 11000; i ++) {
messageDigest.update(input);
messageDigest.update(buffer);
messageDigest.digest();
}
System.out.println("== Array Tests ==");
arrayTest( 16);
arrayTest( 64);
arrayTest( 256);
arrayTest(1024);
arrayTest(8192);
System.out.println("== Buffer Tests ==");
bufferTest( 16);
bufferTest( 64);
bufferTest( 256);
bufferTest(1024);
bufferTest(8192);
System.exit(0);
}
private static void stopTest() {
running = false;
}
private static void startTest(long blockSize) {
System.out.print(blockSize + " Byte ... ");
startTime = System.currentTimeMillis();
rounds = 0;
CancelTestTimerTask cancelTestTimerTask = new CancelTestTimerTask();
timer.schedule(cancelTestTimerTask, 1000 * testSeconds);
running = true;
}
private static void testResult(long blockSize) {
long stopTime = System.currentTimeMillis();
long time = stopTime - startTime;
long speed = ((long)1000 * blockSize * rounds) / time;
messageDigest.reset();
System.out.println(numberFormat.format(rounds) + " rounds = " + numberFormat.format(speed) + " Bytes/s");
}
private static void arrayTest(long blockSize) {
byte[] input = new byte[(int)blockSize];
startTest(blockSize);
while (running) {
messageDigest.update(input);
messageDigest.digest();
rounds++;
}
testResult(blockSize);
}
private static void bufferTest(int blockSize) {
ByteBuffer buffer = ByteBuffer.allocate(blockSize);
startTest(blockSize);
while (running) {
messageDigest.update(buffer);
messageDigest.digest();
buffer.clear();
rounds++;
}
testResult(blockSize);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
new MD5Speed();
}
}