To Pool or not to Pool java crypto service provide

2019-03-10 16:33发布

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

标签: java pool
2条回答
等我变得足够好
2楼-- · 2019-03-10 16:55

This test seems to be in favour of caching

long t0 = System.currentTimeMillis();
byte[] bytes = new byte[100];
MessageDigest md = MessageDigest.getInstance("MD5");
for(int i = 0; i < 1000000; i++) {
    //MessageDigest md = MessageDigest.getInstance("MD5");
    md.reset();
    md.update(bytes);
    md.digest();
}
System.out.println(System.currentTimeMillis() - t0);

When md is outside the loop it prints 579, when inside - 953.

查看更多
看我几分像从前
3楼-- · 2019-03-10 17:04

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

}
查看更多
登录 后发表回答