Параллельная обработка потока и обработка пула потоков против последовательной обработки - PullRequest
0 голосов
/ 04 мая 2018

Я только что оценил, какой из фрагментов кода работает лучше в Java 8.

Фрагмент 1 (обработка в основном потоке):

public long doSequence() {
    DoubleStream ds = IntStream.range(0, 100000).asDoubleStream();
    long startTime = System.currentTimeMillis();
    final AtomicLong al = new AtomicLong();
    ds.forEach((num) -> {
        long n1 = new Double (Math.pow(num, 3)).longValue();
        long n2 = new Double (Math.pow(num, 2)).longValue();
        al.addAndGet(n1 + n2);
    });
    System.out.println("Sequence");
    System.out.println(al.get());
    long endTime = System.currentTimeMillis();
    return (endTime - startTime);
}

Фрагмент 2 (обработка в параллельных потоках):

public long doParallel() {
    long startTime = System.currentTimeMillis();
    final AtomicLong al = new AtomicLong();
    DoubleStream ds = IntStream.range(0, 100000).asDoubleStream();
    ds.parallel().forEach((num) -> {
        long n1 = new Double (Math.pow(num, 3)).longValue();
        long n2 = new Double (Math.pow(num, 2)).longValue();
        al.addAndGet(n1 + n2);
    });
    System.out.println("Parallel");
    System.out.println(al.get());
    long endTime = System.currentTimeMillis();
    return (endTime - startTime);
}

Фрагмент 3 (обработка параллельных потоков из пула потоков):

public long doThreadPoolParallel() throws InterruptedException, ExecutionException {
    ForkJoinPool customThreadPool = new ForkJoinPool(4);
    DoubleStream ds = IntStream.range(0, 100000).asDoubleStream();
    long startTime = System.currentTimeMillis();
    final AtomicLong al = new AtomicLong();
    customThreadPool.submit(() -> ds.parallel().forEach((num) -> {
        long n1 = new Double (Math.pow(num, 3)).longValue();
        long n2 = new Double (Math.pow(num, 2)).longValue();
        al.addAndGet(n1 + n2);
    })).get();
    System.out.println("Thread Pool");
    System.out.println(al.get());
    long endTime = System.currentTimeMillis();
    return (endTime - startTime);
}

Вывод здесь:

Parallel
6553089257123798384
34 <--34 milli seconds

Thread Pool
6553089257123798384
23 <--23 milli seconds

Sequence
6553089257123798384
12 <--12 milli seconds!

То, что я ожидал, было

1) Время обработки с использованием пула потоков должно быть минимальным, но это не так. (Обратите внимание, что я не включил время создания пула потоков, поэтому оно должно быть быстрым)

2) Никогда не ожидал, что код, выполняющийся в последовательности, будет самым быстрым, что должно быть причиной этого.

Я использую четырехъядерный процессор.

Благодарим за любую помощь, чтобы объяснить вышеуказанную двусмысленность!

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Ваше сравнение не идеальное, конечно, из-за отсутствия прогрева виртуальной машины. Когда я просто повторяю казни, я получаю разные результаты:

System.out.println(doParallel());
System.out.println(doThreadPoolParallel());
System.out.println(doSequence());
System.out.println("-------");
System.out.println(doParallel());
System.out.println(doThreadPoolParallel());
System.out.println(doSequence());
System.out.println("-------");
System.out.println(doParallel());
System.out.println(doThreadPoolParallel());
System.out.println(doSequence());

Результаты:

Parallel
6553089257123798384
65
Thread Pool
6553089257123798384
13
Sequence
6553089257123798384
14
-------
Parallel
6553089257123798384
9
Thread Pool
6553089257123798384
4
Sequence
6553089257123798384
8
-------
Parallel
6553089257123798384
8
Thread Pool
6553089257123798384
3
Sequence
6553089257123798384
8

Как отметил @Erwin в комментариях, пожалуйста, проверьте ответы на на этот вопрос (в данном случае правило 1), чтобы узнать, как правильно выполнить этот сравнительный анализ.

Параллелизм параллельного потока по умолчанию не обязательно совпадает с параллелизмом, обеспечиваемым пулом fork-join с таким количеством потоков, сколько имеется ядер на компьютере, хотя разница между результатами все еще незначительна, когда я переключаюсь с вашего Настраиваемый пул к общему пулу вилок

0 голосов
/ 04 мая 2018

AtomicLong.addAndGet требуется синхронизация потоков - каждый поток должен видеть результат предыдущего addAndGet - вы можете рассчитывать на правильность итоговой суммы.

Хотя это не традиционная synchronized синхронизация, у нее все еще есть издержки. В JDK7 addAndGet использовал спин-блокировку в коде Java. В JDK8 он превратился во внутреннее, которое затем реализуется инструкцией LOCK:XADD, генерируемой HotSpot на платформе Intel.

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

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

Ссылки:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...