0

I have a question, I am learning about CompletableFuture of Java 8, I did a dummy with One method running with runAsync of Completable future, it is a simple for 0 to 10 and in paralen a for o to 5 In the second method I run the same for to 0 to 20, but the method of runAsyn takes longer than the other method, It is normal?

Shouldn't the asynchronous method last the same or less than the other method?

Here is the code.

public class Sample{

public static void main(String x[]) throws InterruptedException {
    
    runAsync();
    System.out.println("========== SECOND TESTS ==========");
    runSync();
}

static void runAsync() throws InterruptedException {
    long startTimeOne = System.currentTimeMillis();

    CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
        for (int i = 0; i < 10L; i++) {
            System.out.println(" Async One");
        }
    });
    for (int i = 0; i < 5; i++) {
        System.out.println("two");
    }
    System.out.println("It is ready One? (1) " + cf.isDone());
    System.out.println("It is ready One? (2)" + cf.isDone());
    System.out.println("It is ready One? (3)" + cf.isDone());
    System.out.println("It is ready One? (4)" + cf.isDone());
    System.out.println("It is ready One? (5)" + cf.isDone());
    System.out.println("It is ready One? (6)" + cf.isDone());
    long estimatedTimeOne = System.currentTimeMillis() - startTimeOne;
    System.out.println("Total time async: " + estimatedTimeOne);

}

static void runSync() {
    long startTimeTwo = System.currentTimeMillis();

    for (int i = 0; i < 20; i++) {
        System.out.println("No async");
    }
    long estimatedTimeTwo = System.currentTimeMillis() - startTimeTwo;
    System.out.println("Total time no async: " + estimatedTimeTwo);

}

}

The normal for waste 1 milisecond and the runAsync waste 54 miliseconds

Here is the result screenshot

Naman
  • 27,789
  • 26
  • 218
  • 353
  • Welcome to SO! When you're dealing with numbers as tiny as 5 and 10, doing CPU-bound work and printing to output, async is totally pointless. You'll need to find an appropriate use case for it before it makes any sense to benchmark. – ggorlen Jul 28 '20 at 04:49
  • You will never get a faster result if you wait for the completion of your async task. Async does nothing faster than sync, it simply does it a-synchronous, meaning that your current thread can do stuff while sone other code runs – Felix Jul 28 '20 at 05:05

1 Answers1

2

First, you are violating basic rules mentioned in How do I write a correct micro-benchmark in Java?

Most notably, you’re running both approaches within the same runtime and allow them to affect each other.

Besides that, you are getting an output that is a sequence of messages, which is showing the fundamental problem of your operation: you can not print concurrently. The output system itself has to ensure that the printing will end up showing a sequential behavior.

When you are performing actions that can’t run in parallel through a concurrent framework, you can’t gain performance, you can only add thread communication overhead.

Besides that, the operations are not even the same:

  • the action you are passing to runAsync uses 10L as end boundary, in other words, is performing a long comparison where all other loops use int

  • "It is ready One? (6)" + cf.isDone() is performing two operations that do not appear in the sequential variant. First, polling the status of the CompletableFuture, which must be done with inter-thread semantics. Second, it bears string concatenation. Both are potentially expensive operations

  • The async variant is printing 21 messages whereas the sequential is printing 20. Even the total amount of characters to print is roughly 50% more in the async operation

These points may serve as examples of how easily you can do things wrong in a manual benchmark. But they do not affect the outcome significantly, due to the fundamental aspect mentioned before them. You can’t gain a performance advantage of doing the printing asynchronously at all.

Note that the output is quite consistent in your specific case. Since the common Fork/Join thread pool has not been used before your asynchronous operation, it needs to start a new thread when you submit your job, which takes so long that the subsequent local loop printing "two" completes before the asynchronous operation even starts. The next operation, polling cf.isDone() and performing string concatenation, on the other hand, is so slow, that the asynchronous operation completes entirely before these six print statements complete.

When you change the code to

CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Async One");
    }
});
for(int i = 0; i < 10; i++) {
    System.out.println("two");
}
cf.join();

you still can’t get a performance advantage, but the performance difference will be much smaller. When you add a statement like

ForkJoinPool.commonPool().execute(() -> System.out.println());

at the beginning of the main method, to ensure that the thread pool does not need to get initialized within the measured method, the perceived overhead may even reduce further.

Further, you may swap the order of the runAsync(); and runSync(); method invocations in the main method, to see how first-time execution effects influence the result when you run the two methods within the same JVM.

This all is not enough to make it a reliable benchmark but should help to understand the things that will go wrong when not understanding the pitfalls of doing a micro-benchmark.

Holger
  • 285,553
  • 42
  • 434
  • 765