Method 1
The usual, very fast, and works great.
public static int loops = 500;
private static ExecutorService customPool = Executors.newFixedThreadPool(loops);
.
.
Instant start = Instant.now();
LongSummaryStatistics stats = LongStream.range(0, loops).boxed()
.map(number -> CompletableFuture.supplyAsync(() -> DummyProcess.slowNetworkCall(number), customPool))
.collect(Collectors.toList()).stream() // collect first, else will be sequential
.map(CompletableFuture::join)
.mapToLong(Long::longValue)
.summaryStatistics();
log.info("cf completed in :: {}, summaryStats :: {} ", Duration.between(start, Instant.now()).toMillis(), stats);
// ... cf completed in :: 1054, summaryStats :: LongSummaryStatistics{count=500, sum=504008, min=1000, average=1008.016000, max=1017}
I understand that if I don't collect the stream first, then by nature of laziness, the stream will spring up CompletableFutures one by one, and behave synchronously. So, as an experiment:
Method 2
Remove the intermediate collect step, but make the stream parallel also! :
Instant start = Instant.now();
LongSummaryStatistics stats = LongStream.range(0, loops).boxed()
.parallel()
.map(number -> CompletableFuture.supplyAsync(() -> DummyProcess.slowNetworkCall(number), customPool))
.map(CompletableFuture::join) // direct join
.mapToLong(Long::longValue).summaryStatistics();
log.info("cfps_directJoin completed in :: {}, summaryStats :: {} ", Duration.between(start, Instant.now()).toMillis(), stats);
// ... cfps_directJoin completed in :: 8098, summaryStats :: LongSummaryStatistics{count=500, sum=505002, min=1000, average=1010.004000, max=1015}
Summary:
- Method 1 :: 1 second
- Method 2 :: 8 seconds
A pattern I observed:
- the parallelstream approach "batches" 60 calls at onces, so with 500 loops, 500/60 ~ 8 batches, each taking 1 second, thus total 8
- SO, when I reduce the loop count to 300, there are 300/60 = 5 batches, and it takes 5 seconds to complete actually.
So, the question is:
Why is there this batching of calls in the parallel + direct collection approach?
For completion, here's my dummy network call method:
public static Long slowNetworkCall(Long i) {
Instant start = Instant.now();
log.info(" {} going to sleep..", i);
try {
TimeUnit.MILLISECONDS.sleep(1000); // 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(" {} woke up..", i);
return Duration.between(start, Instant.now()).toMillis();
}