Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6314710

java.security.MessageDigestSpi: re-use byte[] for better performance

XMLWordPrintable

    • 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();
          }
      }

            andreas Andreas Sterbenz
            tbell Tim Bell
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: