【问题标题】:ExecutorService slow multi thread performanceExecutorService 多线程性能慢
【发布时间】:2011-11-01 14:33:29
【问题描述】:

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

我在ExecutorService's surprising performance break-even point --- rules of thumb? 阅读了另一个帖子,并尝试通过使用批处理执行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() 替换为 Random 之后)

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

【问题讨论】:

    标签: java multithreading concurrency executorservice executor


    【解决方案1】:

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

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

    最后,如果您想对大量数字求和,则无需保存它们并在以后对它们进行求和。您可以随时求和。

    【讨论】:

      【解决方案2】:

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

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

      读作:该方法是同步的,因此可能只有一个线程能够同时有效地使用它。所以你做了一堆开销来分配任务,只是为了迫使它们再次串行运行。

      【讨论】:

        【解决方案3】:

        Math.random() 已同步。同步的全部意义在于减慢速度,以免它们发生冲突。使用不同步的东西和/或为每个线程提供自己的对象来使用,例如新的Random

        【讨论】:

        • 啊,你是对的!我没有意识到 Math.random() 是同步的。一旦我为每个 Worker 放置新的 Random 对象,性能就会大大提高
        • 只是一个简单的问题,如果我尝试共享 Random 对象,性能仍然会受到影响。你知道为什么会这样吗? Random.nextDouble 不同步,它调用 Random.next(int) 进而调用 AtomicLong.compareAndSet.. 我不明白为什么这会影响性能
        • 我猜是因为你又回到了让多个线程再次争夺同一个资源:在这种情况下是 AtomicLong。一次只有一个线程可以更新其值,并且每次调用 nextDouble() 都会更新两次。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-16
        • 1970-01-01
        • 2020-09-24
        • 2012-09-01
        相关资源
        最近更新 更多