Многопоточность всегда обеспечивает лучшую производительность, чем одиночная резьба?

Я знаю, что ответ Нет, вот пример Почему один поток быстрее, чем многопоточность в Java?.

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

Вопросы

  • Есть ли еще случаи, когда один поток будет быстрее, чем многопоточность?

  • Когда мы должны отказаться от многопоточности и использовать только один поток для достижения нашей цели?

Хотя вопрос отмечен , также можно обсудить за пределами Java. Было бы здорово, если бы у нас был небольшой пример, чтобы объяснить в ответе.

Ответ 1

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

  • Несколько потоков могут позволить вам делать что-то параллельно, если ваш процессор имеет более одного ядра. Таким образом, в идеальном мире, например, калорирование некоторых простых чисел, может быть в 4 раза быстрее, используя 4 потока, если ваш процессор имеет 4 ядра, и ваш алгоритм работает действительно параллельно.
  • Если вы запускаете больше потоков по мере того, как ядра доступны, управление потоками вашей ОС будет тратить все больше времени на Thread-Switches и в такой эффективности, когда ваш CPU (CPU) становится хуже.
  • Если компилятор, кеш процессора и/или среда выполнения реализовали то, что вы запускаете более одного потока, обращаясь к одной и той же области данных в памяти, работает в другом режиме оптимизации: пока компиляция/время выполнения уверены, что только один поток доступа к данным, может избежать слишком часто записывать данные в extenral RAM и может эффективно использовать кэш L1 вашего CPU. Если нет: необходимо активировать семафоры, а также кэшировать данные чаще из кэша L1/L2 в оперативную память.

Итак, мои уроки, извлеченные из многопоточности с большим количеством парраллелей, были следующими:

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

Здесь небольшая программа (javafx) для игры. Это:

  • Выделяет массив байтов размером 100.000.000, заполненный случайными байтами
  • Предоставляет метод, подсчитывающий количество бит, заданных в этом массиве
  • Метод позволяет считать каждый бит n-го байта
  • count (0,1) будет считать все байтовые байты
  • count (0,4) будет считать 0 ', 4', 8 'байтовые биты, позволяющие проводить параллельный чередование

Использование MacPro (4 ядра) приводит к:

  • Запуск одного потока, count (0,1) требует 1326ms для подсчета всех 399993625 бит.
  • Выполнение двух потоков, подсчет (0,2) и подсчет (1,2) в параллельном режиме требуют 920 мс
  • Запуск четырех потоков, требуется 618 мс
  • Запуск восьми потоков, требуется 631 мс

enter image description hereenter image description hereenter image description hereenter image description here

Изменение способа подсчета, например. приращение общего общего целого числа (AtomicInteger или synchronized) значительно изменяет производительность многих потоков.

public class MulithreadingEffects extends Application {
    static class ParallelProgressBar extends ProgressBar {
        AtomicInteger myDoneCount = new AtomicInteger();
        int           myTotalCount;
        Timeline      myWhatcher = new Timeline(new KeyFrame(Duration.millis(10), e -> update()));
        BooleanProperty running = new SimpleBooleanProperty(false);

        public void update() {
            setProgress(1.0*myDoneCount.get()/myTotalCount);
            if (myDoneCount.get() >= myTotalCount) {
                myWhatcher.stop();
                myTotalCount = 0;
                running.set(false);
            }
        }

        public boolean isRunning() { return myTotalCount > 0; }
        public BooleanProperty runningProperty() { return running; }

        public void start(int totalCount) {
            myDoneCount.set(0);
            myTotalCount = totalCount;
            setProgress(0.0);
            myWhatcher.setCycleCount(Timeline.INDEFINITE);
            myWhatcher.play();
            running.set(true);
        }

        public void add(int n) {
            myDoneCount.addAndGet(n);
        }
    }

    int mySize = 100000000;
    byte[] inData = new byte[mySize];
    ParallelProgressBar globalProgressBar = new ParallelProgressBar();
    BooleanProperty iamReady = new SimpleBooleanProperty(false);
    AtomicInteger myCounter = new AtomicInteger(0);

    void count(int start, int step) {
        new Thread(""+start){
            public void run() {
                int count = 0;
                int loops = 0;
                for (int i = start; i < mySize; i+=step) {
                    for (int m = 0x80; m > 0; m >>=1) {
                        if ((inData[i] & m) > 0) count++;
                    }
                    if (loops++ > 99) {
                        globalProgressBar.add(loops);
                        loops = 0;
                    }
                }
                myCounter.addAndGet(count);
                globalProgressBar.add(loops);
            }
        }.start();
    }

    void pcount(Label result, int n) {
        result.setText("("+n+")");
        globalProgressBar.start(mySize);
        long start = System.currentTimeMillis();
        myCounter.set(0);
        globalProgressBar.runningProperty().addListener((p,o,v) -> {
            if (!v) {
                long ms = System.currentTimeMillis()-start;
                result.setText(""+ms+" ms ("+myCounter.get()+")");
            }
        });
        for (int t = 0; t < n; t++) count(t, n);
    }

    void testParallel(VBox box) {
        HBox hbox = new HBox();

        Label result = new Label("-");
        for (int i : new int[]{1, 2, 4, 8}) {
            Button run = new Button(""+i);
            run.setOnAction( e -> {
                if (globalProgressBar.isRunning()) return;
                pcount(result, i);
            });
            hbox.getChildren().add(run);
        }

        hbox.getChildren().addAll(result);
        box.getChildren().addAll(globalProgressBar, hbox);
    }


    @Override
    public void start(Stage primaryStage) throws Exception {        
        primaryStage.setTitle("ProgressBar's");

        globalProgressBar.start(mySize);
        new Thread("Prepare"){
            public void run() {
                iamReady.set(false);
                Random random = new Random();
                random.setSeed(4711);
                for (int i = 0; i < mySize; i++) {
                    inData[i] = (byte)random.nextInt(256);
                    globalProgressBar.add(1);
                }
                iamReady.set(true);
            }
        }.start();

        VBox box = new VBox();
        Scene scene = new Scene(box,400,80,Color.WHITE);
        primaryStage.setScene(scene);

        testParallel(box);
        GUIHelper.allowImageDrag(box);

        primaryStage.show();   
    }

    public static void main(String[] args) { launch(args); }
}

Ответ 2

Не все алгоритмы могут обрабатываться параллельно (алгоритмы строго последовательные, где P = 0 в закон Amdahl) или, по крайней мере, неэффективно (см. P-complete). Другие алгоритмы более подходят для параллельного выполнения (крайние случаи называются "неловко параллельными" ).

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

Когда мы должны отказаться от многопоточности и использовать только один поток для достижения нашей цели?

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

Ответ 3

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

Ответ 4

Threading - это использование свободных ресурсов для обработки большего количества работы. Если у вас нет свободных ресурсов, многопоточность не имеет преимуществ, поэтому накладные расходы на самом деле заставят ваше общее время выполнения работать дольше.

Например, если у вас есть набор задач для выполнения, и они рассчитаны на интенсивность процессора. Если у вас есть один процессор, многопоточность, вероятно, не ускорит этот процесс (хотя вы никогда не узнаете, пока не проверите). Я ожидаю, что он немного замедлится. Вы меняете то, как работа делится, но никаких изменений в емкости. Если у вас есть 4 задачи для работы с одним процессором, их серийный номер равен 1 * 4. Если вы сделаете их параллельно, вы выйдете в основном 4 * 1, что будет одинаково. Кроме того, накладные расходы на объединение результатов и переключение контекста.

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

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

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

Ответ 5

Как уже упоминалось в комментарии от @Jim Mischel, вы можете использовать

Закон Amdahl

чтобы вычислить это. Закон Amdahl гласит, что ускорение, полученное от добавления процессоров для решения задачи, - это

enter image description here

где

N - количество процессоров, а

P - это доля кода, которая может выполняться параллельно (0.. 1)

Теперь, если T - это время, необходимое для выполнения задачи на одном процессоре, а O - это общее время работы (создание и настройка второй поток, связь,...), один поток быстрее, если

T < T/S (2) + O

или, после переупорядочения, если

O/T > P/2

Когда соотношение Время работы/время выполнения больше P/2, один поток выполняется быстрее.

Ответ 6

Есть ли еще случаи, когда один поток будет быстрее, чем многопоточность?

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

Этот вопрос очень широк... Вы считаете Unit Tests в качестве приложений? Если это так, вероятно, есть больше приложений, которые используют отдельные потоки, потому что любая сложная система будет (надеюсь) не менее 1 unit test. Вы считаете каждую программу Hello world style как другое приложение или то же самое? Если приложение удалено, оно все еще считается?

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

Когда мы должны отказаться от многопоточности и использовать только один поток для достижения нашей цели?

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

Может ли ваша проблема быть разбита на части, которые можно обрабатывать одновременно? Не на ухищренном пути, как разбить Hello World на два потока, где один поток ждет другого для печати. Но каким образом 2+ темы могли бы выполнить задачу более эффективно, чем одна?

Даже если задача легко распараллеливается, это не значит, что она должна быть. Я мог бы использовать многопоточное приложение, которое постоянно тронуло тысячи новых сайтов, чтобы получить мои новости. Для меня лично это было бы сосать, потому что это съели бы мою трубку, и я не смог бы получить мой FPS. Для CNN это может быть именно то, что они хотят, и построит мэйнфрейм для его размещения.

Можете ли вы сузить свои вопросы?