From 43332bb6d0c20ae507fca21793f039eef394fdc0 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Wed, 1 Jun 2011 11:44:10 +0000
Subject: [PATCH]     * Crypto:       - Use java.security.MessageDigest instead
 of bundled GNU SHA-256 code         if available, which it should always be. 
        5 to 20% faster on Oracle JVM; 40 to 60% on Harmony;         5 to 15%
 on JamVM; 20x (!) on GIJ.       - Use java.security.MessageDigest instead of
 bundled Bitzi SHA-1 code         if available on non-Oracle JVMs, which it
 should always be.         Not faster on Oracle JVM; 30 to 60% faster on
 Harmony;         15 to 20% on JamVM; 10-15x (!) on GIJ.

---
 .../java/src/org/klomp/snark/MetaInfo.java    |  12 +-
 .../java/src/org/klomp/snark/Storage.java     |   3 +-
 core/java/src/net/i2p/crypto/DSAEngine.java   |  11 +-
 core/java/src/net/i2p/crypto/SHA1.java        |  98 ++++++++++++
 .../src/net/i2p/crypto/SHA256Generator.java   | 139 ++++++++++++++++--
 history.txt                                   |  11 ++
 .../src/net/i2p/router/RouterVersion.java     |   2 +-
 7 files changed, 243 insertions(+), 33 deletions(-)

diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
index 5889db89e..28677adda 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
@@ -24,7 +24,6 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -401,7 +400,7 @@ public class MetaInfo
 ****/
   
   private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
-    SHA1 sha1 = new SHA1();
+    MessageDigest sha1 = SHA1.getInstance();
 
     sha1.update(bs, off, length);
     byte[] hash = sha1.digest();
@@ -519,18 +518,11 @@ public class MetaInfo
     }
     byte[] infoBytes = BEncoder.bencode(info);
     //_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
-    try
-      {
-        MessageDigest digest = MessageDigest.getInstance("SHA");
+        MessageDigest digest = SHA1.getInstance();
         byte hash[] = digest.digest(infoBytes);
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("info hash: " + I2PSnarkUtil.toHex(hash));
         return hash;
-      }
-    catch(NoSuchAlgorithmException nsa)
-      {
-        throw new InternalError(nsa.toString());
-      }
   }
 
   /** @since 0.8.5 */
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index 1183e9bf3..9c8f02cc4 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -23,6 +23,7 @@ package org.klomp.snark;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -165,7 +166,7 @@ public class Storage
    */
   private byte[] fast_digestCreate() throws IOException {
     // Calculate piece_hashes
-    SHA1 digest = new SHA1();
+    MessageDigest digest = SHA1.getInstance();
 
     byte[] piece_hashes = new byte[20 * pieces];
 
diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java
index d09d58ce1..e59e56343 100644
--- a/core/java/src/net/i2p/crypto/DSAEngine.java
+++ b/core/java/src/net/i2p/crypto/DSAEngine.java
@@ -32,6 +32,7 @@ package net.i2p.crypto;
 import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigInteger;
+import java.security.MessageDigest;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.Hash;
@@ -228,25 +229,25 @@ public class DSAEngine {
     
     /** @return hash SHA-1 hash, NOT a SHA-256 hash */
     public SHA1Hash calculateHash(InputStream in) {
-        SHA1 digest = new SHA1();
+        MessageDigest digest = SHA1.getInstance();
         byte buf[] = new byte[64];
         int read = 0;
         try {
             while ( (read = in.read(buf)) != -1) {
-                digest.engineUpdate(buf, 0, read);
+                digest.update(buf, 0, read);
             }
         } catch (IOException ioe) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Unable to hash the stream", ioe);
             return null;
         }
-        return new SHA1Hash(digest.engineDigest());
+        return new SHA1Hash(digest.digest());
     }
 
     /** @return hash SHA-1 hash, NOT a SHA-256 hash */
     public static SHA1Hash calculateHash(byte[] source, int offset, int len) {
-        SHA1 h = new SHA1();
-        h.engineUpdate(source, offset, len);
+        MessageDigest h = SHA1.getInstance();
+        h.update(source, offset, len);
         byte digested[] = h.digest();
         return new SHA1Hash(digested);
     }
diff --git a/core/java/src/net/i2p/crypto/SHA1.java b/core/java/src/net/i2p/crypto/SHA1.java
index 6dbdee074..c8c54306d 100644
--- a/core/java/src/net/i2p/crypto/SHA1.java
+++ b/core/java/src/net/i2p/crypto/SHA1.java
@@ -15,10 +15,17 @@ package net.i2p.crypto;
  * put("MessageDigest.SHA-1", "com.bitzi.util.SHA1");
  */
 //package com.bitzi.util;
+
 import java.security.DigestException;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
  
+import net.i2p.data.Base64;
+
 /**
+ * NOTE: As of 0.8.7, use getInstance() instead of new SHA1(), which will
+ * return the JVM's MessageDigest if it is faster.
+ *
  * <p>The FIPS PUB 180-2 standard specifies four secure hash algorithms (SHA-1,
  * SHA-256, SHA-384 and SHA-512) for computing a condensed representation of
  * electronic data (message).  When a message of any length < 2^^64 bits (for
@@ -85,8 +92,27 @@ public final class SHA1 extends MessageDigest implements Cloneable {
      */
     private int hA, hB, hC, hD, hE;
  
+    private static final boolean _useBitzi;
+    static {
+        // oddly, Bitzi is faster than Oracle - see test results below
+        boolean useBitzi = true;
+        String vendor = System.getProperty("java.vendor");
+        if (vendor.startsWith("Apache") ||                      // Harmony
+            vendor.startsWith("GNU Classpath") ||               // JamVM
+            vendor.startsWith("Free Software Foundation")) {    // gij
+            try {
+                MessageDigest.getInstance("SHA-1");
+                useBitzi = false;
+            } catch (NoSuchAlgorithmException e) {}
+        }
+        //if (useBitzi)
+        //    System.out.println("INFO: Using Bitzi SHA-1");
+        _useBitzi = useBitzi;
+    }
+
     /**
      * Creates a SHA1 object with default initial state.
+     * NOTE: Use getInstance() to get the fastest implementation.
      */
     public SHA1() {
         super("SHA-1");
@@ -94,6 +120,19 @@ public final class SHA1 extends MessageDigest implements Cloneable {
         init();
     }
  
+    /**
+     *  @return the fastest digest, either new SHA1() or MessageDigest.getInstance("SHA-1")
+     *  @since 0.8.7
+     */
+    public static MessageDigest getInstance() {
+        if (!_useBitzi) {
+            try {
+                return MessageDigest.getInstance("SHA-1");
+            } catch (NoSuchAlgorithmException e) {}
+        }
+        return new SHA1();
+    }
+
     /**
      * Clones this object.
      */
@@ -699,4 +738,63 @@ public final class SHA1 extends MessageDigest implements Cloneable {
         hD += d;
         hC += /* c= */ (c << 30) | (c >>> 2);
     }
+
+    private static final int RUNS = 100000;
+
+    /**
+     *  Test the GNU and the JVM's implementations for speed
+     *
+     *  Results: 2011-05 eeepc Atom
+     *  <pre>
+     *  JVM	strlen	GNU ms	JVM  ms 
+     *	Oracle	387	  1406	 2357
+     *	Oracle	 40	   522	  475
+     *	Harmony	387	  5504	 3474
+     *	Harmony	 40	  4396	 1593
+     *	JamVM	387	 25578	21966
+     *	JamVM	 40	  5380	 4195
+     *	gij	387	 47225	 3501
+     *	gij	 40	  9861    919
+     *  </pre>
+     *
+     *  @since 0.8.7
+     */
+    public static void main(String args[]) {
+        if (args.length <= 0) {
+            System.err.println("Usage: SHA1 string");
+            return;
+        }
+
+        byte[] data = args[0].getBytes();
+        SHA1 gnu = new SHA1();
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < RUNS; i++) {
+            gnu.update(data, 0, data.length);
+            byte[] sha = gnu.digest();
+            if (i == 0)
+                System.out.println("SHA1 [" + args[0] + "] = [" + Base64.encode(sha) + "]");
+            gnu.reset();
+        }
+        long time = System.currentTimeMillis() - start;
+        System.out.println("Time for " + RUNS + " SHA-256 computations:");
+        System.out.println("GNU time (ms): " + time);
+
+        start = System.currentTimeMillis();
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            System.err.println("Fatal: " + e);
+            return;
+        }
+        for (int i = 0; i < RUNS; i++) {
+            md.reset();
+            byte[] sha = md.digest(data);
+            if (i == 0)
+                System.out.println("SHA1 [" + args[0] + "] = [" + Base64.encode(sha) + "]");
+        }
+        time = System.currentTimeMillis() - start;
+
+        System.out.println("JVM time (ms): " + time);
+    }
 }
diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java
index 87955646b..a62f2129b 100644
--- a/core/java/src/net/i2p/crypto/SHA256Generator.java
+++ b/core/java/src/net/i2p/crypto/SHA256Generator.java
@@ -2,6 +2,8 @@ package net.i2p.crypto;
 
 import gnu.crypto.hash.Sha256Standalone;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.concurrent.LinkedBlockingQueue;
 
 import net.i2p.I2PAppContext;
@@ -9,14 +11,29 @@ import net.i2p.data.Base64;
 import net.i2p.data.Hash;
 
 /** 
- * Defines a wrapper for SHA-256 operation.  All the good stuff occurs
- * in the GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone}
+ * Defines a wrapper for SHA-256 operation.
  * 
+ * As of release 0.8.7, uses java.security.MessageDigest by default.
+ * If that is unavailable, it uses
+ * GNU-Crypto {@link gnu.crypto.hash.Sha256Standalone}
  */
 public final class SHA256Generator {
-    private final LinkedBlockingQueue<Sha256Standalone> _digestsGnu;
+    private final LinkedBlockingQueue<MessageDigest> _digests;
+
+    private static final boolean _useGnu;
+    static {
+        boolean useGnu = false;
+        try {
+            MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            useGnu = true;
+            System.out.println("INFO: Using GNU SHA-256");
+        }
+        _useGnu = useGnu;
+    }
+
     public SHA256Generator(I2PAppContext context) {
-        _digestsGnu = new LinkedBlockingQueue(32);
+        _digests = new LinkedBlockingQueue(32);
     }
     
     public static final SHA256Generator getInstance() {
@@ -36,10 +53,10 @@ public final class SHA256Generator {
      * Calculate the hash and cache the result.
      */
     public final Hash calculateHash(byte[] source, int start, int len) {
-        Sha256Standalone digest = acquireGnu();
+        MessageDigest digest = acquire();
         digest.update(source, start, len);
         byte rv[] = digest.digest();
-        releaseGnu(digest);
+        release(digest);
         //return new Hash(rv);
         return Hash.create(rv);
     }
@@ -47,31 +64,121 @@ public final class SHA256Generator {
     /**
      * Use this if you only need the data, not a Hash object.
      * Does not cache.
+     * @param out needs 32 bytes starting at outOffset
      */
     public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) {
-        Sha256Standalone digest = acquireGnu();
+        MessageDigest digest = acquire();
         digest.update(source, start, len);
         byte rv[] = digest.digest();
-        releaseGnu(digest);
+        release(digest);
         System.arraycopy(rv, 0, out, outOffset, rv.length);
     }
     
-    private Sha256Standalone acquireGnu() {
-        Sha256Standalone rv = _digestsGnu.poll();
+    private MessageDigest acquire() {
+        MessageDigest rv = _digests.poll();
         if (rv != null)
             rv.reset();
         else
-            rv = new Sha256Standalone();
+            rv = getDigestInstance();
         return rv;
     }
     
-    private void releaseGnu(Sha256Standalone digest) {
-        _digestsGnu.offer(digest);
+    private void release(MessageDigest digest) {
+        _digests.offer(digest);
     }
     
+    private static MessageDigest getDigestInstance() {
+        if (!_useGnu) {
+            try {
+                return MessageDigest.getInstance("SHA-256");
+            } catch (NoSuchAlgorithmException e) {}
+        }
+        return new GnuMessageDigest();
+    }
+
+    /**
+     *  Wrapper to make Sha256Standalone a MessageDigest
+     *  @since 0.8.7
+     */
+    private static class GnuMessageDigest extends MessageDigest {
+        private final Sha256Standalone _gnu;
+
+        protected GnuMessageDigest() {
+            super("SHA-256");
+            _gnu = new Sha256Standalone();
+        }
+
+        protected byte[] engineDigest() {
+            return _gnu.digest();
+        }
+
+        protected void engineReset() {
+            _gnu.reset();
+        }
+
+        protected void engineUpdate(byte input) {
+            _gnu.update(input);
+        }
+
+        protected void engineUpdate(byte[] input, int offset, int len) {
+            _gnu.update(input, offset, len);
+        }
+    }
+
+    private static final int RUNS = 100000;
+
+    /**
+     *  Test the GNU and the JVM's implementations for speed
+     *
+     *  Results: 2011-05 eeepc Atom
+     *  <pre>
+     *  JVM	strlen	GNU ms	JVM  ms 
+     *	Oracle	387	  3861	 3565
+     *	Oracle	 40	   825	  635
+     *	Harmony	387	  8082	 5158
+     *	Harmony	 40	  4137	 1753
+     *	JamVM	387	 36301	34100
+     *	JamVM	 40	  7022	 6016
+     *	gij	387	125833	 4342
+     *	gij	 40	 22417    988
+     *  </pre>
+     */
     public static void main(String args[]) {
-        I2PAppContext ctx = I2PAppContext.getGlobalContext();
-        for (int i = 0; i < args.length; i++)
-            System.out.println("SHA256 [" + args[i] + "] = [" + Base64.encode(ctx.sha().calculateHash(args[i].getBytes()).getData()) + "]");
+        if (args.length <= 0) {
+            System.err.println("Usage: SHA256Generator string");
+            return;
+        }
+
+        byte[] data = args[0].getBytes();
+        Sha256Standalone gnu = new Sha256Standalone();
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < RUNS; i++) {
+            gnu.update(data, 0, data.length);
+            byte[] sha = gnu.digest();
+            if (i == 0)
+                System.out.println("SHA256 [" + args[0] + "] = [" + Base64.encode(sha) + "]");
+            gnu.reset();
+        }
+        long time = System.currentTimeMillis() - start;
+        System.out.println("Time for " + RUNS + " SHA-256 computations:");
+        System.out.println("GNU time (ms): " + time);
+
+        start = System.currentTimeMillis();
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            System.err.println("Fatal: " + e);
+            return;
+        }
+        for (int i = 0; i < RUNS; i++) {
+            md.reset();
+            byte[] sha = md.digest(data);
+            if (i == 0)
+                System.out.println("SHA256 [" + args[0] + "] = [" + Base64.encode(sha) + "]");
+        }
+        time = System.currentTimeMillis() - start;
+
+        System.out.println("JVM time (ms): " + time);
     }
 }
diff --git a/history.txt b/history.txt
index 941f5b433..5e4612090 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,14 @@
+2011-06-01 zzz
+    * Crypto:
+      - Use java.security.MessageDigest instead of bundled GNU SHA-256 code
+        if available, which it should always be.
+        5 to 20% faster on Oracle JVM; 40 to 60% on Harmony;
+        5 to 15% on JamVM; 20x (!) on GIJ.
+      - Use java.security.MessageDigest instead of bundled Bitzi SHA-1 code
+        if available on non-Oracle JVMs, which it should always be.
+        Not faster on Oracle JVM; 30 to 60% faster on Harmony;
+        15 to 20% on JamVM; 10-15x (!) on GIJ.
+
 2011-06-01 sponge
     * ConfigClients stopClient stubbed out.
 
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 31c35f8c3..ea17ad55f 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 14;
+    public final static long BUILD = 15;
 
     /** for example "-test" */
     public final static String EXTRA = "";
-- 
GitLab