Home > Blockchain >  CompletableFuture working slowly on complete
CompletableFuture working slowly on complete

Time:11-15

So I've been running some tests on the CompletableFuture class, and I've stumbled upon some weird behaviour I can't explain.

Issue

I've managed to reduce the issue down to this code snippet

        long nanos = System.nanoTime();
        CompletableFuture<UUID> someFuture = CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            return UUID.randomUUID();
        });
        someFuture.get(5, TimeUnit.SECONDS);
        System.out.println((System.nanoTime() - nanos) / 1_000_000   "ms");

I would expect this piece of code to run in around 10 milliseconds. For some reason, running it ends up taking about 500ms! It should be noted that this only happens in the first execution of the code, and subsequent executions seem to be taking about 10-15 millis (which I am expecting).

My question is, what can I do to get rid of this, either by optimizing it or by not using CompletableFuture.

What I've tried

  • I've tried using Runnable's instead of lambdas, no effect.
  • I've tried overriding the get(long timeout, TimeUnit unit) of the CompletableFuture to poll the result once every millisecond. This took the latency discrepancy down from 12ms to about 2-3ms, however, it didn't affect the cold boot times.
  • I've tried starting up new Threads to complete the future, calling ForkJoinPool.commonPool(), tried using Executors.newCachedThreadPool() and Executors.newFixedThreadPool(2);. No effect as far as I can tell.

Background

The scenario I'm coming from (to avoid XY problems) is that I am trying to make a simple request-reply model through TCP. I mark each request with a UUID and hold it in a ConcurrentHashMap<UUID, CompletableFuture<String>> and when the packet comes back, I fetch the completable future from the map and complete it with the reply. I am doing it like this since replies aren't expected to come in order. I am experiencing 100ms latency (consistently though, not just the first execution) even though both the server and the client are running on localhost. I've double-checked that the issue is not networking wise, and as far as I can tell, the bottleneck comes down to the CompletableFuture being slow. Is such a Map a good way to approach this problem? If so, how can I get rid of the latency, if not, how would I go about designing something like this?

CodePudding user response:

From the docs of UUID.randomUUID:

Static factory to retrieve a type 4 (pseudo randomly generated) UUID. The UUID is generated using a cryptographically strong pseudo random number generator.

A disadvantage of cryptographic RNGs is that they are slow. Because it is a pseudo-random number generator, the slowness of generating good random numbers only exists when crearing the random number generator.

If you take a look at the OpenJDK sources of UUID.randomUUID, you can see that the first random number generator is obtained using a simple but effective singleton that is loaded whenever it is called first (when the class Holder is loaded):

private static class Holder {
        static final SecureRandom numberGenerator = new SecureRandom();
    } 
SecureRandom ng = Holder.numberGenerator;

As a solution, you can just run UUID.randomUUID() at the start of the program in the background doing the work earlier.

If you don't need cryptographic strong UUIDs, you can create those using a non-cryptographically strong RNG:

Random rand=new Random();//do this once

//do this whenever you need a new UUID
UUID uuid=new UUID(rand.nextLong(),rand.nextLong());
  • Related