ExecutorService медленная многопоточная производительность - PullRequest
6 голосов
/ 23 августа 2011

Я пытаюсь выполнить простой расчет (он вызывает 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 () является синхронизированным методом .. после замены Math.random () новым объектом Random для каждого потока производительность улучшается

Выводдля нового кода (после замены Math.random () на Random для каждого потока)

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

Ответы [ 3 ]

12 голосов
/ 23 августа 2011

Math.random () синхронизирован.Единственная цель синхронизированного состоит в том, чтобы замедлить вещи, чтобы они не сталкивались.Используйте что-то, что не синхронизировано, и / или предоставьте каждому потоку свой собственный объект для работы, например, новый Случайный .

3 голосов
/ 23 августа 2011

Вы бы хорошо прочитали содержимое другой ветки.Там есть много хороших советов.

Возможно, самая значительная проблема с вашим тестом производительности заключается в том, что в соответствии с контрактом Math.random (): «Этот метод правильно синхронизирован, чтобы обеспечить правильное использование более чем одним потоком.Однако, если многим потокам нужно генерировать псевдослучайные числа с большой скоростью, это может уменьшить конкуренцию за то, что у каждого потока будет свой собственный генератор псевдослучайных чисел "

.скорее всего сможет использовать его одновременно.Таким образом, вы выполняете кучу накладных расходов, чтобы распределить задачи, только чтобы заставить их снова запускаться последовательно.

1 голос
/ 23 августа 2011

Когда вы используете несколько потоков, вы должны знать о накладных расходах при использовании дополнительных потоков.Вам также необходимо определить, работает ли ваш алгоритм, который может быть выполнен параллельно или нет.Поэтому вам нужно иметь работу, которая может быть запущена одновременно, достаточно большой, чтобы она превышала накладные расходы при использовании нескольких потоков.

В этом случае самый простой обходной путь - использовать отдельный Random в каждом потоке.Проблема, с которой вы столкнулись, состоит в том, что в качестве микро-теста ваш цикл фактически ничего не делает, а JIT очень хорошо отбрасывает код, который ничего не делает.Обходной путь для этого - суммировать случайные результаты и возвращать их из call(), поскольку этого обычно достаточно, чтобы JIT не сбрасывал код.

Наконец, если вы хотите суммировать много чисел, вы не должныне нужно их спасать и суммировать позже.Вы можете суммировать их, как вы идете.

...