Более 2 потоков работают медленнее, чем 1 или 2 потока, если только Thread.sleep (1) не помещен в метод run () потока - PullRequest
0 голосов
/ 27 апреля 2019

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

Однако один поток всегда быстрее, независимо от того, выбираю ли я 2 потока (редактировать. 2 потока быстрее, но ненамного, в то время как 4 потока медленнее, чем 1 поток, и я понятия не имею, почему. (Я мог бы даже сказать, что чем больше тем, тем медленнее он становится). Я надеюсь, что кто-то может объяснить. Может быть, я делаю что-то не так.

Ниже мой код, который я написал до сих пор. Я использую ThreadPoolExecutor для выполнения задач (одна задача = одна последовательность Коллатца для одного числа в интервале).

Класс Коллатц:

    public class ParallelCollatz implements Runnable {
    private long result;
    private long inputNum;

    public long getResult() {
        return result;
    }
    public void setResult(long result) {
        this.result = result;
    }
    public long getInputNum() {
        return inputNum;
    }
    public void setInputNum(long inputNum) {
        this.inputNum = inputNum;
    }
    public void run() {

        //System.out.println("number:" + inputNum);
        //System.out.println("Thread:" + Thread.currentThread().getId());
        //int j=0;
        //if(Thread.currentThread().getId()==11) {
        //  ++j;
        //  System.out.println(j);
        //}

            long result = 1;

            //main recursive computation
            while (inputNum > 1) {

                if (inputNum % 2 == 0) {
                    inputNum = inputNum / 2;
                } else {
                    inputNum = inputNum * 3 + 1;
                }
                ++result;
            }
           // try {
                //Thread.sleep(10);
            //} catch (InterruptedException e) {
                // TODO Auto-generated catch block
        //      e.printStackTrace();
            //}
            this.result=result;
            return;
        }

}

И основной класс, в котором я запускаю потоки (да, сейчас я создаю два списка с одинаковыми номерами, так как после запуска с одним потоком начальные значения теряются):

        ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
    ThreadPoolExecutor executor2 = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);

    List<ParallelCollatz> tasks = new ArrayList<ParallelCollatz>();
    for(int i=1; i<=1000000; i++) {
        ParallelCollatz task = new ParallelCollatz();
        task.setInputNum((long)(i+1000000));
        tasks.add(task);

    }


    long startTime = System.nanoTime();
    for(int i=0; i<1000000; i++) {
        executor.execute(tasks.get(i));

    }

    executor.shutdown();
    boolean tempFirst=false;
    try {
        tempFirst =executor.awaitTermination(5, TimeUnit.HOURS);
    } catch (InterruptedException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    System.out.println("tempFirst " + tempFirst);
     long endTime = System.nanoTime();
    long    durationInNano = endTime - startTime;
    long    durationInMillis = TimeUnit.NANOSECONDS.toMillis(durationInNano);  //Total execution time in nano seconds
        System.out.println("laikas " +durationInMillis);


        List<ParallelCollatz> tasks2 = new ArrayList<ParallelCollatz>();
        for(int i=1; i<=1000000; i++) {
            ParallelCollatz task = new ParallelCollatz();
            task.setInputNum((long)(i+1000000));
            tasks2.add(task);

        }


        long startTime2 = System.nanoTime();
        for(int i=0; i<1000000; i++) {
            executor2.execute(tasks2.get(i));

        }

        executor2.shutdown();
        boolean temp =false;
        try {
             temp=executor2.awaitTermination(5, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("temp "+ temp);
         long endTime2 = System.nanoTime();
            long durationInNano2 = endTime2 - startTime2;
            long durationInMillis2 = TimeUnit.NANOSECONDS.toMillis(durationInNano2);  //Total execution time in nano seconds
            System.out.println("laikas2 " +durationInMillis2);

Например, работа с одним потоком завершается за 3280 мс. Работает с двумя потоками 3437мс. Должен ли я рассмотреть другую параллельную структуру для расчета каждого элемента?

EDIT Clarrification. Я не пытаюсь распараллелить отдельные последовательности, но интервал чисел, когда у каждого числа есть своя последовательность (которая не связана с другими числами)

EDIT2

Сегодня я запустил программу на хорошем ПК с 6 ядрами и 12 логическими процессорами, и проблема не устранена. У кого-нибудь есть идея, где может быть проблема? Я также обновил свой код. По некоторым причинам 4 потока делают хуже, чем 2 потока (даже хуже, чем 1 поток). Я также применил то, что было дано в ответе, но без изменений.

Другое Править Что я заметил, что если я добавлю Thread.sleep (1) в мой метод ParallelCollatz, то производительность будет постепенно увеличиваться с увеличением количества потоков. Возможно, эта деталь говорит кому-то, что не так? Однако независимо от того, сколько задач я выполняю, если нет Thread.Sleep (1) 2 потока выполняют быстрее всего, 1 поток занимает второе место, а другие зависают примерно одинаковое количество миллисекунд, но медленнее, чем потоки 1 и 2.

Новое редактирование Я также попытался поместить больше задач (для цикла для вычисления не 1, а 10 или 100 последовательностей Коллатца) в метод run () класса Runnable, чтобы сам поток выполнял больше работы. К сожалению, это тоже не помогло. Возможно я запускаю задачи неправильно? У кого-нибудь есть идеи?

EDIT Так что может показаться, что после добавления большего количества задач в метод run это немного исправляется, но для большего количества потоков проблема все еще остается 8+. Я все еще задаюсь вопросом, является ли это причиной того, что для создания и запуска потоков требуется больше времени, чем для выполнения задачи? Или я должен создать новый пост с этим вопросом?

1 Ответ

2 голосов
/ 28 апреля 2019

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

executor.shutdown() не ожидает завершения всех задач. Вам нужно позвонить executor.awaitTermination после этого.

executor.shutdown();
executor.awaitTermination(5, TimeUnit.HOURS);

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#shutdown()

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

Я считаю, что основными причинами являются следующие:

  • Запуск JVM и время компиляции JIT.Сначала код выполняется в интерпретированном режиме.
  • результат расчета игнорируется.У меня нет интуиции, что удаляется JIT и что мы на самом деле измеряем.
  • printlines в коде

Чтобы проверить это, я преобразовал ваш тест в JMH.В частности:

  • Я преобразовал исполняемый файл в вызываемый, и я возвращаю сумму результатов, чтобы предотвратить встраивание (альтернативно, вы можете использовать BlackHole из JMH)
  • Мои задачи не имеютЯ переместил все движущиеся части в локальные переменные.Для очистки задач GC не нужен.
  • Я все еще создаю исполнителей в каждом раунде.Это не идеально, но я решил оставить все как есть.

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

Benchmark                  Mode  Cnt    Score    Error  Units
SpeedTest.multipleThreads  avgt   20  559.996 ± 20.181  ms/op
SpeedTest.singleThread     avgt   20  562.048 ± 16.418  ms/op

Обновлен код:

public class ParallelCollatz implements Callable<Long> {

    private final long inputNumInit;

    public ParallelCollatz(long inputNumInit) {
        this.inputNumInit = inputNumInit;
    }


    @Override
    public Long call() {
        long result = 1;
        long inputNum = inputNumInit;
        //main recursive computation
        while (inputNum > 1) {

            if (inputNum % 2 == 0) {
                inputNum = inputNum / 2;
            } else {
                inputNum = inputNum * 3 + 1;
            }
            ++result;
        }
        return result;
    }

}

и сам тест:

@State(Scope.Benchmark)
public class SpeedTest {
private static final int NUM_TASKS = 1000000;

    private static List<ParallelCollatz> tasks = buildTasks();

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @SuppressWarnings("unused")
    public long singleThread() throws Exception {
        ThreadPoolExecutor executorOneThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
        return measureTasks(executorOneThread, tasks);
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @SuppressWarnings("unused")
    public long multipleThreads() throws Exception {
        ThreadPoolExecutor executorMultipleThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
        return measureTasks(executorMultipleThread, tasks);
    }

    private static long measureTasks(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
        long sum = runTasksInExecutor(executor, tasks);
       return sum;
    }

    private static long runTasksInExecutor(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
        List<Future<Long>> futures = new ArrayList<>(NUM_TASKS);
        for (int i = 0; i < NUM_TASKS; i++) {
            Future<Long> f = executor.submit(tasks.get(i));
            futures.add(f);
        }
        executor.shutdown();

        boolean tempFirst = false;
        try {
            tempFirst = executor.awaitTermination(5, TimeUnit.HOURS);
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        long sum = 0l;
        for (Future<Long> f : futures) {
            sum += f.get();
        }
        //System.out.println(sum);
        return sum;
    }

    private static List<ParallelCollatz> buildTasks() {
        List<ParallelCollatz> tasks = new ArrayList<>();
        for (int i = 1; i <= NUM_TASKS; i++) {
            ParallelCollatz task = new ParallelCollatz((long) (i + NUM_TASKS));

            tasks.add(task);

        }
        return tasks;
    }

}
...