it-swarm.com.ru

Java Stream API: почему различие между последовательным и параллельным режимом выполнения?

Из Поток Javadoc :

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

Мои предположения:

  1. Нет никакой функциональной разницы между последовательными/параллельными потоками. Выход никогда не зависит от режима выполнения.
  2. Параллельный поток всегда предпочтителен, учитывая соответствующее количество ядер и размер проблемы, чтобы оправдать накладные расходы из-за увеличения производительности.
  3. Мы хотим написать код один раз и запускать где угодно, не заботясь об аппаратном обеспечении (в конце концов, это Java).

Предполагая, что эти предположения верны (нет ничего плохого в небольшом количестве мета-предположений), какова ценность показа режима выполнения в API?

Кажется, что вы должны просто иметь возможность объявить Stream, и выбор последовательного/параллельного выполнения должен выполняться автоматически на следующем уровне, либо с помощью библиотечного кода, либо самой JVM как функции ядер, доступных во время выполнения, размера проблемы и т. д.

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

Даже если есть сценарий, в котором вы намеренно хотите жестко закодировать использование последовательного потока - почему для этой цели существует не просто подинтерфейс SequentialStream, а не загрязнение Stream переключателем режима выполнения?

18
davnicwil

Кажется, что вы должны просто иметь возможность объявлять поток, и выбор последовательного/параллельного выполнения должен выполняться автоматически на следующем уровне, либо с помощью библиотечного кода, либо самой JVM как функции ядер, доступных во время выполнения, размера проблемы и т. д.

Реальность такова, что а) потоки являются библиотекой и не имеют особой магии JVM, и б) вы не можете по-настоящему спроектировать библиотеку, достаточно умную, чтобы автоматически определить, какое именно решение принято в данном конкретном случае. Не существует разумного способа оценить, насколько дорогой будет отдельная функция без ее запуска - даже если вы могли бы проанализировать ее реализацию, чего вы не можете - и теперь вы вводите эталон в каждую потоковую операцию, пытаясь выяснить если распараллеливание будет стоить издержек параллелизма. Это просто не практично, особенно если учесть, что вы заранее не знаете, насколько плохи издержки параллелизма.

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

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

По сути, это достаточно сложная проблема, которую вы должны перенести на программиста.

26
Louis Wasserman

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

5
Tagir Valeev

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

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

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

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

Мы хотим написать код один раз и запускать где угодно, не заботясь о аппаратное обеспечение (это Java, в конце концов).

Так как только программист знает о; Стоит ли выполнять эту задачу параллельно/последовательно, независимо от процессоров. Так что Java API предоставил разработчику оба варианта.

import Java.util.ArrayList;
import Java.util.List;

/*
 * Performance test over internal(parallel/sequential) and external iterations.
 * https://docs.Oracle.com/javase/tutorial/collections/streams/parallelism.html
 * 
 * 
 * Parallel computing involves dividing a problem into subproblems, 
 * solving those problems simultaneously (in parallel, with each subproblem running in a separate thread),
 *  and then combining the results of the solutions to the subproblems. Java SE provides the fork/join framework, 
 *  which enables you to more easily implement parallel computing in your applications. However, with this framework, 
 *  you must specify how the problems are subdivided (partitioned). 
 *  With aggregate operations, the Java runtime performs this partitioning and combining of solutions for you.
 * 
 * Limit the parallelism that the ForkJoinPool offers you. You can do it yourself by supplying the -Djava.util.concurrent.ForkJoinPool.common.parallelism=1,
 *  so that the pool size is limited to one and no gain from parallelization
 *  
 *  @see ForkJoinPool
 *  https://docs.Oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
 *  
 *  ForkJoinPool, that pool creates a fixed number of threads (default: number of cores) and 
 *  will never create more threads (unless the application indicates a need for those by using managedBlock).
 *   *  http://stackoverflow.com/questions/10797568/what-determines-the-number-of-threads-a-Java-forkjoinpool-creates
 *  
 */
public class IterationThroughStream {
    private static boolean found = false;
    private static List<Integer> smallListOfNumbers = null;
    public static void main(String[] args) throws InterruptedException {


        // TEST_1
        List<String> bigListOfStrings = new ArrayList<String>();
        for(Long i = 1l; i <= 1000000l; i++) {
            bigListOfStrings.add("Counter no: "+ i);
        }

        System.out.println("Test Start");
        System.out.println("-----------");
        long startExternalIteration = System.currentTimeMillis();
        externalIteration(bigListOfStrings);
        long endExternalIteration = System.currentTimeMillis();
        System.out.println("Time taken for externalIteration(bigListOfStrings) is :" + (endExternalIteration - startExternalIteration) + " , and the result found: "+ found);

        long startInternalIteration = System.currentTimeMillis();
        internalIteration(bigListOfStrings);
        long endInternalIteration = System.currentTimeMillis();
        System.out.println("Time taken for internalIteration(bigListOfStrings) is :" + (endInternalIteration - startInternalIteration) + " , and the result found: "+ found);





        // TEST_2
        smallListOfNumbers = new ArrayList<Integer>();
        for(int i = 1; i <= 10; i++) {
            smallListOfNumbers.add(i);
        }

        long startExternalIteration1 = System.currentTimeMillis();
        externalIterationOnSleep(smallListOfNumbers);
        long endExternalIteration1 = System.currentTimeMillis();
        System.out.println("Time taken for externalIterationOnSleep(smallListOfNumbers) is :" + (endExternalIteration1 - startExternalIteration1));

        long startInternalIteration1 = System.currentTimeMillis();
        internalIterationOnSleep(smallListOfNumbers);
        long endInternalIteration1 = System.currentTimeMillis();
        System.out.println("Time taken for internalIterationOnSleep(smallListOfNumbers) is :" + (endInternalIteration1 - startInternalIteration1));




        // TEST_3
        Thread t1 = new Thread(IterationThroughStream :: internalIterationOnThread);
        Thread t2 = new Thread(IterationThroughStream :: internalIterationOnThread);
        Thread t3 = new Thread(IterationThroughStream :: internalIterationOnThread);
        Thread t4 = new Thread(IterationThroughStream :: internalIterationOnThread);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        Thread.sleep(30000);
    }


    private static boolean externalIteration(List<String> bigListOfStrings) {
        found = false;
        for(String s : bigListOfStrings) {
            if(s.equals("Counter no: 1000000")) {
                found = true;
            }
        }
        return found;
    }

    private static boolean internalIteration(List<String> bigListOfStrings) {
        found = false;
        bigListOfStrings.parallelStream().forEach(
                (String s) -> { 
                    if(s.equals("Counter no: 1000000")){  //Have a breakpoint to look how many threads are spawned.
                        found = true;
                    }

                }
            );
        return found;       
    }


    private static boolean externalIterationOnSleep(List<Integer> smallListOfNumbers) {
        found = false;
        for(Integer s : smallListOfNumbers) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return found;
    }

    private static boolean internalIterationOnSleep(List<Integer> smallListOfNumbers) {
        found = false;
        smallListOfNumbers.parallelStream().forEach( //Removing parallelStream() will behave as single threaded (sequential access).
                (Integer s) -> {
                    try {
                        Thread.sleep(100); //Have a breakpoint to look how many threads are spawned.
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            );
        return found;       
    }

    public static void internalIterationOnThread() {
        smallListOfNumbers.parallelStream().forEach(
                (Integer s) -> {
                    try {
                        /*
                         * DANGEROUS
                         * This will tell you that if all the 7 FJP(Fork join pool) worker threads are blocked for one single thread (e.g. t1), 
                         * then other normal three(t2 - t4) thread wont execute, will wait for FJP worker threads. 
                         */
                        Thread.sleep(100); //Have a breakpoint here.
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            );
    }
}
3
Kanagavelu Sugumar

Кажется, что вы должны просто иметь возможность объявлять поток, и выбор последовательного/параллельного выполнения должен выполняться автоматически на следующем уровне, либо с помощью библиотечного кода, либо самой JVM как функции ядер, доступных во время выполнения, размера проблемы и т. д.

Чтобы добавить к уже предоставленным ответам:

Это довольно смелое предположение. Представьте себе, что вы моделируете настольную игру для обучения некоторой форме ИИ, довольно просто распараллелить выполнение различных прохождений - просто создайте новый экземпляр и дайте ему работать в своем собственном потоке. Так как он не имеет общего состояния с другим прохождением, вам даже не нужно учитывать многопоточность в игровой логике. С другой стороны, если вы распараллелите саму игровую логику, вы получите всевозможные проблемы с многопоточностью и, скорее всего, заплатите высокую цену за сложность и даже производительность.

Контроль над поведением потоков дает вам (соответственно ограниченную) гибкость, которая сама по себе является ключевой характеристикой для хорошего дизайна библиотеки.

0
naze