Почему однопоточная операция занимает в 3 раза больше времени, чем безпоточная? - PullRequest
0 голосов
/ 15 октября 2019

Я тестирую параллелизм в Java, моя цель - определить, действительно ли полезно иметь несколько потоков, однако я получаю результаты, которые не суммируются. Я пытаюсь оптимизировать функцию факториала, для этого конкретного теста я использую 1e9! и я хочу результат по модулю 1e9 + 7, чтобы он не переполнялся. Во-первых, я разделил число на основе number_of_threads и назначил каждому потоку их работу соответственно. Затем я делаю это нормально и сравниваю полученное время. Кажется, что когда number_of_threads = 4, я получаю лучшие результаты, чем версия без потоков, что имеет смысл, так как мой процессор имеет 4 ядра. Как и ожидалось, любое количество потоков больше 4 имеет более медленное время по сравнению с только 4. Однако при выполнении этого с менее чем 4 потоками результаты становятся слишком большими, например, с 1 потоком, я ожидаю, что он будет длиться так же, как и при выполнении. это без нити + накладные расходы. Без потока я получаю 6,2 секунды и 19,3 с 1 потоком, что является слишком большой разницей для того, чтобы это было просто издержками.

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

public class Calc implements Runnable{
    long min, max, mod, res;
    Res r;
    public Calc(long min, long max, long mod, Res r) {
        this.min = min;
        this.max = max;
        this.mod = mod;
        res = 1;
        this.r = r;
    }
    public void run() {
        for(long i = min; i <= max; i++) {
            res *= i;
            res %= mod;
        }
        r.addup(res);
    }
}

public class Res{
    long result;
    long mod;
    public Res(long mod) {
        result = 1;
        this.mod = mod;
    }
    public synchronized void addup(long add) {
        result *= add;
        result %= mod;
    }
    public long getResult() {
        return result;
    }
}

public class Main{
    public static void main(String args[]) {
        long startTime = System.nanoTime();
        final long factorial = 1000000000L;
        final long modulo = 1000000007L;
        Res res = new Res(modulo);
        int number_of_threads = 1;
        Thread[] c = new Thread[number_of_threads];
        long min = 1, max = factorial/(long)number_of_threads;
        long cant = max;
        for(int i = 0; i < number_of_threads; i++) {
            if((long)i < (factorial % number_of_threads))max++;
            c[i] = new Thread(new Calc(min, max, modulo, res));
            c[i].start();
            min = max +1;
            max += cant;
        }
        for(int i = 0; i < number_of_threads; i++) {
            try {
                c[i].join();
            }catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(res.getResult());
        long endTime   = System.nanoTime();
        long totalTime = endTime - startTime;
        System.out.println((double)totalTime/1000000000L);
    }
}

Когда number_of_threads = 1, я получаю 19,3 секунды. Когда number_of_threads = 2, я получаю 10,1 секунды. Когда number_of_threads = 3, я получаю 7,1 секунды. Когда number_of_threads = 4, я получаю 5,4 секунды. И когда я делаю это без потоков, я получаю 6,2 секунды (я вычисляю время на этом с помощью того же метода)

Не должно быть такой большой разницы между только 1 потоком и без потоков, а для 2 и 3темы должны быть быстрее, чем нет темы. Почему это так и есть ли способ это исправить? Спасибо.

Редактировать: Добавление без версии потока

public class Main{
    public static void main(String args[]) {
        long startTime = System.nanoTime();
        final long factorial = 1000000000L;
        final long modulo = 1000000007L;

        long res = 1;
        for(long i = 1; i <= factorial; i++) {
            res *= i;
            res %= modulo;
        }
        System.out.println(res);
        long endTime   = System.nanoTime();
        long totalTime = endTime - startTime;
        System.out.println((double)totalTime/1000000000L);
    }

}

1 Ответ

0 голосов
/ 15 октября 2019

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

Если вы убедитесь, что компилятор не будет знать заранее, какие значениябудет:

class Main{
    public static void main(String args[]) {
        long factorial = Long.parseLong(args[0]);
        long modulo = Long.parseLong(args[1]);
        long startTime = System.nanoTime();

        long res = 1;
        for(long i = 1; i <= factorial; i++) {
            res *= i;
            res %= modulo;
        }
        System.out.println(res);
        long endTime   = System.nanoTime();
        long totalTime = endTime - startTime;
        System.out.println((double)totalTime/1000000000L);
    }

}

Тогда это будет значительно медленнее:

$ java Main 1000000000 1000000007
698611116
11.368487148

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

$ java Main
698611116
10.709532315

Точно так же, если вы измените свой пример с 1 потоком, чтобы использовать .run() вместо .start(), все будет работать в основном потоке, но не будет ускорения в случае с 1 потоком.

...