MultiThreading与SingleThreading

时间:2015-11-18 19:25:31

标签: java multithreading fork-join

我测试了一些简单的条件:
考虑一个长度为10 000 000的int数组。填充:

  1. 使用单(主)线程。
  2. 使用双工作线程并加入它们直到完成。第一个从开始填充到阵列的中间。第二个从结尾到中间。
  3. 使用ExecutorService固定池(2),调用execute并等待终止。
  4. 使用ForkJoinPool,默认工作量(可用处理器数量)
  5. import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.RecursiveAction;
    import java.util.concurrent.TimeUnit;
    
    public class PerformanceTest {
        private static final int ARRAY_LENGTH = 10_000_000;
        private static int[] array;
        private static final int ITERATIONS = 10;
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < ITERATIONS; i++) {
                array = new int[ARRAY_LENGTH];
                long millis = System.currentTimeMillis();
                singleWorkerFill();
                System.out.println("Single worker: " + (System.currentTimeMillis() - millis));
    
                array = new int[ARRAY_LENGTH];
                millis = System.currentTimeMillis();
                doubleWorkerFill();
                System.out.println("Double worker: " + (System.currentTimeMillis() - millis));
    
                array = new int[ARRAY_LENGTH];
                millis = System.currentTimeMillis();
                forkJoinWorkersFill();
                System.out.println("Executor workers: " + (System.currentTimeMillis() - millis));
    
                array = new int[ARRAY_LENGTH];
                millis = System.currentTimeMillis();
                executorWorkersFill();
                System.out.println("ForkJoin workers: " + (System.currentTimeMillis() - millis));
    
                System.out.println("---------------------------------------------");
                Thread.sleep(1000);
            }
        }
    
        private static void singleWorkerFill() {
            for (int i = 0, len = array.length; i < len; i++) {
                array[i] = i;
            }
        }
    
        private static void doubleWorkerFill() throws InterruptedException {
            Thread worker1 = new Thread(new HeadArrayFiller());
            Thread worker2 = new Thread(new TailArrayFiller());
            worker1.start();
            worker2.start();
            worker1.join();
            worker2.join();
        }
    
        private static void executorWorkersFill() throws InterruptedException {
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            executorService.execute(new HeadArrayFiller());
            executorService.execute(new TailArrayFiller());
            executorService.shutdown();
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
        }
    
        private static void forkJoinWorkersFill() throws InterruptedException {
            ForkJoinPool pool = new ForkJoinPool();
            pool.invoke(new HeadArrayFiller());
            pool.invoke(new TailArrayFiller());
            pool.shutdown();
            pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
        }
    
        private static class HeadArrayFiller extends RecursiveAction implements Runnable {
            @Override
            public void run() {
                for (int i = 0, middle = array.length / 2; i <= middle; i++) {
                    array[i] = i;
                }
            }
    
            @Override
            protected void compute() {
                run();
            }
        }
    
        private static class TailArrayFiller extends RecursiveAction implements Runnable {
            @Override
            public void run() {
                for (int i = array.length - 2, middle = array.length / 2; i > middle; i--) {
                    array[i] = i;
                }
            }
    
            @Override
            protected void compute() {
                run();
            }
        }
    }
    

    我预计单线程模型与其他模型没有机会,但事实并非如此。这里的测试结果以毫秒为单位缩放:

    ITERATION #1
    Single worker: 7
    Double worker: 10
    Executor workers: 11
    ForkJoin workers: 6
    ITERATION #2
    Single worker: 6
    Double worker: 4
    Executor workers: 5
    ForkJoin workers: 4
    ITERATION #3
    Single worker: 4
    Double worker: 4
    Executor workers: 5
    ForkJoin workers: 4
    ITERATION #4
    Single worker: 5
    Double worker: 5
    Executor workers: 5
    ForkJoin workers: 4
    ITERATION #5
    Single worker: 5
    Double worker: 5
    Executor workers: 4
    ForkJoin workers: 5
    ITERATION #6
    Single worker: 4
    Double worker: 4
    Executor workers: 5
    ForkJoin workers: 4
    ITERATION #7
    Single worker: 4
    Double worker: 4
    Executor workers: 4
    ForkJoin workers: 5
    ITERATION #8
    Single worker: 4
    Double worker: 4
    Executor workers: 4
    ForkJoin workers: 5
    ITERATION #9
    Single worker: 4
    Double worker: 4
    Executor workers: 4
    ForkJoin workers: 5
    ITERATION #10
    Single worker: 5
    Double worker: 4
    Executor workers: 4
    ForkJoin workers: 4
    

    正如您在启动时注意到单线程模型比多线程双工作者更快。 Fork-join模型似乎是最好的ExecutorService。 我建议对迭代进行一些JIT编译器优化。在测试结束时,它们都非常相似。

    无论如何,主要问题是为什么双线程模型性能与单线程相同(在冷启动时甚至更慢)。我怎样才能达到预期的两倍速度?

    由于

1 个答案:

答案 0 :(得分:1)

对于现代计算机来说,初始化10M整数是一项非常快速的任务,并且在两个独立的核心上并行处理并不能补偿(或仅补偿)启动线程的开销,上下文切换他们之间,协调他们等等。

在每次迭代中开始做更多的工作(例如,睡眠5毫秒),多线程的优势将开始出现。