ExecutorService 多线程性能慢

发布于 2024-11-30 21:51:34 字数 3408 浏览 0 评论 0原文

我正在尝试执行一个简单的计算(它调用 Math.random() 10000000 次)。令人惊讶的是,以简单的方法运行它比使用 ExecutorService 执行得快得多。

我读过另一个线程 ExecutorService 令人惊讶的性能收支平衡点 - -- 经验法则? 并尝试通过使用批处理执行 Callable 来遵循答案,但性能仍然很差

如何根据我当前的情况提高性能 代码?

import java.util.*;
import java.util.concurrent.*;

public class MainTest {
    public static void main(String[]args) throws Exception {
        new MainTest().start();;
    }

    final List<Worker> workermulti = new ArrayList<Worker>();
    final List<Worker> workersingle = new ArrayList<Worker>();
    final int count=10000000;

    public void start() throws Exception {
        int n=2;

        workersingle.add(new Worker(1));
        for (int i=0;i<n;i++) {
            // worker will only do count/n job
            workermulti.add(new Worker(n));
        }

        ExecutorService serviceSingle = Executors.newSingleThreadExecutor();
        ExecutorService serviceMulti = Executors.newFixedThreadPool(n);
        long s,e;
        int tests=10;
        List<Long> simple = new ArrayList<Long>();
        List<Long> single = new ArrayList<Long>();
        List<Long> multi = new ArrayList<Long>();

        for (int i=0;i<tests;i++) {
            // simple
            s = System.currentTimeMillis();
            simple();
            e = System.currentTimeMillis();
            simple.add(e-s);

            // single thread
            s = System.currentTimeMillis();
               serviceSingle.invokeAll(workersingle); // single thread
            e = System.currentTimeMillis();
            single.add(e-s);

            // multi thread
            s = System.currentTimeMillis();
               serviceMulti.invokeAll(workermulti);
            e = System.currentTimeMillis();
            multi.add(e-s);
        }
        long avgSimple=sum(simple)/tests;
        long avgSingle=sum(single)/tests;
        long avgMulti=sum(multi)/tests;
        System.out.println("Average simple: "+avgSimple+" ms");
        System.out.println("Average single thread: "+avgSingle+" ms");
        System.out.println("Average multi thread: "+avgMulti+" ms");

        serviceSingle.shutdown();
        serviceMulti.shutdown();
    }

    long sum(List<Long> list) {
        long sum=0;
        for (long l : list) {
            sum+=l;
        }
        return sum;
    }

    private void simple() {
        for (int i=0;i<count;i++){
            Math.random();
        }
    }

    class Worker implements Callable<Void> {
        int n;

        public Worker(int n) {
            this.n=n;
        }

        @Override
        public Void call() throws Exception {
            // divide count with n to perform batch execution
            for (int i=0;i<(count/n);i++) {
                Math.random();
            }
            return null;
        }
    }
}

此代码的输出

Average simple: 920 ms
Average single thread: 1034 ms
Average multi thread: 1393 ms

编辑:由于 Math.random() 是同步方法,性能受到影响。在为每个线程使用新的 Random 对象更改 Math.random() 后,性能得到改善

新代码的输出(替换 Math .random() 每个线程随机)

Average simple: 928 ms
Average single thread: 1046 ms
Average multi thread: 642 ms

I am trying to execute a simple calculation (it calls Math.random() 10000000 times). Surprisingly running it in simple method performs much faster than using ExecutorService.

I have read another thread at ExecutorService's surprising performance break-even point --- rules of thumb? and tried to follow the answer by executing the Callable using batches, but the performance is still bad

How do I improve the performance based on my current code?

import java.util.*;
import java.util.concurrent.*;

public class MainTest {
    public static void main(String[]args) throws Exception {
        new MainTest().start();;
    }

    final List<Worker> workermulti = new ArrayList<Worker>();
    final List<Worker> workersingle = new ArrayList<Worker>();
    final int count=10000000;

    public void start() throws Exception {
        int n=2;

        workersingle.add(new Worker(1));
        for (int i=0;i<n;i++) {
            // worker will only do count/n job
            workermulti.add(new Worker(n));
        }

        ExecutorService serviceSingle = Executors.newSingleThreadExecutor();
        ExecutorService serviceMulti = Executors.newFixedThreadPool(n);
        long s,e;
        int tests=10;
        List<Long> simple = new ArrayList<Long>();
        List<Long> single = new ArrayList<Long>();
        List<Long> multi = new ArrayList<Long>();

        for (int i=0;i<tests;i++) {
            // simple
            s = System.currentTimeMillis();
            simple();
            e = System.currentTimeMillis();
            simple.add(e-s);

            // single thread
            s = System.currentTimeMillis();
               serviceSingle.invokeAll(workersingle); // single thread
            e = System.currentTimeMillis();
            single.add(e-s);

            // multi thread
            s = System.currentTimeMillis();
               serviceMulti.invokeAll(workermulti);
            e = System.currentTimeMillis();
            multi.add(e-s);
        }
        long avgSimple=sum(simple)/tests;
        long avgSingle=sum(single)/tests;
        long avgMulti=sum(multi)/tests;
        System.out.println("Average simple: "+avgSimple+" ms");
        System.out.println("Average single thread: "+avgSingle+" ms");
        System.out.println("Average multi thread: "+avgMulti+" ms");

        serviceSingle.shutdown();
        serviceMulti.shutdown();
    }

    long sum(List<Long> list) {
        long sum=0;
        for (long l : list) {
            sum+=l;
        }
        return sum;
    }

    private void simple() {
        for (int i=0;i<count;i++){
            Math.random();
        }
    }

    class Worker implements Callable<Void> {
        int n;

        public Worker(int n) {
            this.n=n;
        }

        @Override
        public Void call() throws Exception {
            // divide count with n to perform batch execution
            for (int i=0;i<(count/n);i++) {
                Math.random();
            }
            return null;
        }
    }
}

The output for this code

Average simple: 920 ms
Average single thread: 1034 ms
Average multi thread: 1393 ms

EDIT: performance suffer due to Math.random() being a synchronised method.. after changing Math.random() with new Random object for each thread, the performance improved

The output for the new code (after replacing Math.random() with Random for each thread)

Average simple: 928 ms
Average single thread: 1046 ms
Average multi thread: 642 ms

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

毁虫ゝ 2024-12-07 21:51:34

Math.random() is synchronized. Kind of the whole point of synchronized is to slow things down so they don't collide. Use something that isn't synchronized and/or give each thread its own object to work with, like a new Random.

单身情人 2024-12-07 21:51:34

您最好阅读其他线程的内容。里面有很多好的建议。

也许您的基准测试中最重要的问题是,根据 Math.random() 契约,“此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速度生成伪随机数,它可以减少每个线程拥有自己的伪随机数生成器的争用”

将其理解为:该方法是同步的,因此只有一个线程可能能够同时有效地使用它。因此,您需要花费大量开销来分配任务,结果却迫使它们再次串行运行。

You'd do well to read the contents of the other thread. There's plenty of good tips in there.

Perhaps the most significant issue with your benchmark is that according to the Math.random() contract, "This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator"

Read this as: the method is synchronized, so only one thread is likely to be able to usefully use it at the same time. So you do a bunch of overhead to distribute the tasks, only to force them again to run serially.

浅唱ヾ落雨殇 2024-12-07 21:51:34

当您使用多个线程时,您需要注意使用额外线程的开销。您还需要确定您的算法是否具有可以并行执行的工作。因此,您需要有可以并发运行的工作,该工作足够大,以至于超过使用多个线程的开销。

在这种情况下,最简单的解决方法是在每个线程中使用单独的 Random。您遇到的问题是,作为微基准,您的循环实际上不执行任何操作,而 JIT 非常擅长丢弃不执行任何操作的代码。解决此问题的方法是对随机结果求和并从 call() 返回它,因为这通常足以防止 JIT 丢弃代码。

最后,如果您想对大量数字求和,则无需保存它们并稍后对其求和。您可以边做边总结它们。

When you use multiple threads, you need to be aware of the overhead of using additional threads. You also need to determine if your algorithm has work which can be preformed in parallel or not. So you need to have work which can be run concurrently which is large enough that it will exceed the overhead of using multiple threads.

In this case, the simplest workaround is to use a separate Random in each thread. The problem you have is that as a micro-benchmark, your loop doesn't actually do anything and the JIT is very good at discarding code which doesn't do anything. A workaround for this is to sum the random results and return it from the call() as this is usually enough to prevent the JIT from discarding the code.

Lastly if you want to sum lots of numbers, you don't need to save them and sum them later. You can sum them as you go.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文