Search code examples
javapool

To Pool or not to Pool java crypto service providers


Solution

  • MessageDigest => create new instances as often as needed
  • KeyFactory => use a single shared instance
  • SecureRandom => use a StackObjectPool
  • Cipher => use a StackObjectPool

Question

I face a regular dilemna while coding security frameworks : "to pool or not to pool"

Basically this question is divided on two "groups" :

  1. Group 1 : SecureRandom because the call to nextBytes(...) is synchronized and it could become a bottleneck for a WebApp / a multi-threaded app

  2. Group 2 : Crypto service providers like MessageDigest, Signature, Cipher, KeyFactory, ... (because of the cost of the getInstance() ?)

What is your opinion ?

What are you habits on such questions?

Edit 09/07/2013

I finally took time to test @Qwerky Share class by myself and I find the result quite ... surprising.

The class was lacking my main concern : Pools like GenericObjectPool or StackObjectPool.

So I've reworked the class to test all 4 alternatives :

  • Single shared instance with synchronisation gist
  • new instances inside each loops (I'm not interested in the case when you can pull the digest creation outside the loop) gist
  • GenericObjectPool : gist
  • StackObjectPool : gist

I had to lower the number of loops to 100000 since 1M was taking too much time with pools.

I also added a Thread.yield() at the end of each loop to give the load a nicer shape.

Results (cummulative runtime):

  • MessageDigest
    • new instances : 420 s
    • Single instance : 550 s
    • StackObjectPool : 800 s
    • GenericObjectPool : 1900 s
  • KeyFactory
    • new instances : 400s
    • Single instance : 350 s
    • StackObjectPool : 2900 s
    • GenericObjectPool : 3500 s
  • SecureRandom
    • StackObjectPool : 1600 s
    • new instances : 2300 s
    • GenericObjectPool : 2300s
    • Single instance : 2800 s
  • Cipher
    • StackObjectPool : 2800 s
    • GenericObjectPool : 3500 s
    • Single instance : 5100 s
    • new instances : 8000 s

Conclusion

For MessageDigest and KeyFactory, pools are perf killers and are even worse than a single instance with a synchronisation bottleneck, whereas they are really usefull when it comes to SecureRandom and Cipher


Solution

  • If you give 100 threads access to a shared MessageDigest and get them to calculate 1,000,000 hashes each then on my machine the first thread finishes in 70,160ms and the last finishes in 98,748ms.

    If the threads create a new instance of MessageDigest each time, then the first thread finishes in 43,392ms and the last 58,691ms.

    Edit:
    In fact with this example, with only two threads the example creating new instances runs quicker.

    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Share {
    
      final byte[] bytes = new byte[100];
      final MessageDigest sharedDigest;
      final ExecutorService pool;
      int threads = 100;
    
      Share() throws NoSuchAlgorithmException {
        sharedDigest = MessageDigest.getInstance("MD5");
        pool = Executors.newFixedThreadPool(threads);
      }
    
      void go() {
    
        for (int i=0; i<threads; i++) {
          pool.execute(new Runnable() {
            public void run() {
              long start = System.currentTimeMillis();
              for (int i=0; i<1000000; i++) {
                /*
                synchronized (sharedDigest) {
                  sharedDigest.reset();
                  sharedDigest.update(bytes);
                  sharedDigest.digest();
                }*/
                try {
                  MessageDigest digest = MessageDigest.getInstance("MD5");
                  digest.reset();
                  digest.update(bytes);
                  digest.digest();
                } catch (Exception ex) {
                  ex.printStackTrace();
                }
              }
              long end = System.currentTimeMillis();
              System.out.println(end-start);
              pool.shutdown();
            }
          });
        }
    
      }
    
      public static void main(String[] args) throws Exception {
        Share share = new Share();
        share.go();
      }
    
    }