Completable future浪费更多时间?

huangapple go评论49阅读模式
英文:

Completable future waste more time?

问题

I have a question, I am learning about CompletableFuture of Java 8, I did a dummy with One method running with runAsync of CompletableFuture, it is a simple for 0 to 10, and in parallel, a for 0 to 5. In the second method, I run the same loop from 0 to 20, but the runAsync method takes longer than the other method. Is this normal?

Shouldn't the asynchronous method take the same or less time 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 < 10; i++) {
                System.out.println("Async One");
            }
        });
        for (int i = 0; i < 5; i++) {
            System.out.println("two");
        }
        System.out.println("Is it ready One? (1) " + cf.isDone());
        System.out.println("Is it ready One? (2)" + cf.isDone());
        System.out.println("Is it ready One? (3)" + cf.isDone());
        System.out.println("Is it ready One? (4)" + cf.isDone());
        System.out.println("Is it ready One? (5)" + cf.isDone());
        System.out.println("Is it 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 loop takes 1 millisecond, and the runAsync loop takes 54 milliseconds.

Here is the result screenshot

英文:

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(&quot;========== SECOND TESTS ==========&quot;);
runSync();
}
static void runAsync() throws InterruptedException {
long startTimeOne = System.currentTimeMillis();
CompletableFuture&lt;Void&gt; cf = CompletableFuture.runAsync(() -&gt; {
for (int i = 0; i &lt; 10L; i++) {
System.out.println(&quot; Async One&quot;);
}
});
for (int i = 0; i &lt; 5; i++) {
System.out.println(&quot;two&quot;);
}
System.out.println(&quot;It is ready One? (1) &quot; + cf.isDone());
System.out.println(&quot;It is ready One? (2)&quot; + cf.isDone());
System.out.println(&quot;It is ready One? (3)&quot; + cf.isDone());
System.out.println(&quot;It is ready One? (4)&quot; + cf.isDone());
System.out.println(&quot;It is ready One? (5)&quot; + cf.isDone());
System.out.println(&quot;It is ready One? (6)&quot; + cf.isDone());
long estimatedTimeOne = System.currentTimeMillis() - startTimeOne;
System.out.println(&quot;Total time async: &quot; + estimatedTimeOne);
}
static void runSync() {
long startTimeTwo = System.currentTimeMillis();
for (int i = 0; i &lt; 20; i++) {
System.out.println(&quot;No async&quot;);
}
long estimatedTimeTwo = System.currentTimeMillis() - startTimeTwo;
System.out.println(&quot;Total time no async: &quot; + estimatedTimeTwo);
}

}

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

Here is the result screenshot

答案1

得分: 2

I can provide a translation of the text you provided:

"首先,您正在违反https://stackoverflow.com/q/504103/2711488中提到的基本规则。

最值得注意的是,您在同一运行时中同时运行两种方法,并允许它们相互影响。

除此之外,您正在获取一系列消息的输出,这显示了您的操作的根本问题:您无法并行打印。输出系统本身必须确保打印最终会显示出顺序行为。

当您执行不能通过并发框架并行运行的操作时,您无法获得性能提升,只能增加线程通信开销。

此外,这些操作甚至不相同:

  • 您传递给runAsync的操作使用10L作为结束边界,换句话说,执行long比较,而所有其他循环都使用int

  • &quot;它准备好了吗?(6)&quot; + cf.isDone()执行了两个在顺序变体中没有出现的操作。首先,轮询CompletableFuture的状态,这必须使用跨线程语义完成。其次,它包含字符串连接。这两者都是潜在昂贵的操作

  • 异步变体打印了21条消息,而顺序变体打印了20条。即使要打印的字符总数在异步操作中大约多50%

这些观点可能作为如何在手动基准测试中轻松出错的示例。但由于之前提到的基本方面的影响不大,它们并不会显著影响结果。您无法通过异步方式打印来获得性能优势。

请注意,在您的特定情况下,输出非常一致。由于通用的Fork/Join线程池在异步操作之前尚未被使用,当您提交作业时,它需要启动一个新线程,这需要很长时间,以至于在异步操作开始之前,后续的本地循环打印“two”已经完成。另一方面,下一个操作,轮询cf.isDone()和执行字符串连接,速度非常慢,以至于在这六个打印语句完成之前,异步操作已经完全完成。

当您将代码更改为

CompletableFuture&lt;Void&gt; cf = CompletableFuture.runAsync(() -&gt; {
    for (int i = 0; i &lt; 10; i++) {
        System.out.println(&quot;Async One&quot;);
    }
});
for(int i = 0; i &lt; 10; i++) {
    System.out.println(&quot;two&quot;);
}
cf.join();

您仍然无法获得性能优势,但性能差异会小得多。当您在main方法的开头添加像这样的语句

ForkJoinPool.commonPool().execute(() -&gt; System.out.println());

以确保线程池不需要在测量方法内初始化时,感知的开销甚至可能进一步减小。

此外,您可以交换main方法中runAsync();runSync();方法调用的顺序,以查看当您在同一JVM中运行这两种方法时,首次执行效果如何影响结果。

所有这些都不足以使其成为可靠的基准测试,但应有助于了解在不理解微基准测试陷阱时会发生什么问题。"

Please note that the translation may not preserve the exact formatting and code examples as in the original text.

英文:

First, you are violating basic rules mentioned in https://stackoverflow.com/q/504103/2711488

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

  • &quot;It is ready One? (6)&quot; + 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 &quot;two&quot; 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&lt;Void&gt; cf = CompletableFuture.runAsync(() -&gt; {
for (int i = 0; i &lt; 10; i++) {
System.out.println(&quot;Async One&quot;);
}
});
for(int i = 0; i &lt; 10; i++) {
System.out.println(&quot;two&quot;);
}
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(() -&gt; 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.

huangapple
  • 本文由 发表于 2020年7月28日 12:32:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/63127067.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定