开发者

ExecutorService slow multi thread performance

I am trying to execute a simple calculation (it calls Math.random() 10000000 times). Surprisingly running it in simple method perfo开发者_运维技巧rms 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


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜