Использование параллелизма в Java делает программу медленнее (в четыре раза медленнее !!!) - PullRequest
3 голосов
/ 31 мая 2011

Пишу реализацию метода сопряженных градиентов.

Я использую многопоточность Java для обратной замены матрицы. Синхронизация выполняется с использованием CyclicBarrier, CountDownLatch.

Почему синхронизация потоков занимает так много времени? Есть ли другие способы сделать это?

фрагмент кода

private void syncThreads() {

    // barrier.await();

    try {

        barrier.await();

    } catch (InterruptedException e) {

    } catch (BrokenBarrierException e) {

    }

}

Ответы [ 5 ]

6 голосов
/ 31 мая 2011

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

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

final double[] results = new double[10*1000*1000];
{
    long start = System.nanoTime();
    // using a plain loop.
    for(int i=0;i<results.length;i++) {
        results[i] = (double) i * i;
    }
    long time = System.nanoTime() - start;
    System.out.printf("With one thread it took %.1f ns per square%n", (double) time / results.length);
}
{
    ExecutorService ex = Executors.newFixedThreadPool(4);
    long start = System.nanoTime();
    // using a plain loop.
    for(int i=0;i<results.length;i++) {
        final int i2 = i;
        ex.execute(new Runnable() {
            @Override
            public void run() {
                results[i2] = i2 * i2;

            }
        });
    }
    ex.shutdown();
    ex.awaitTermination(1, TimeUnit.MINUTES);
    long time = System.nanoTime() - start;
    System.out.printf("With four threads it took %.1f ns per square%n", (double) time / results.length);
}

печать

With one thread it took 1.4 ns per square
With four threads it took 715.6 ns per square

Использование нескольких потоков намного хуже.

Тем не менее, увеличьте объем работы, выполняемой каждым потоком, и

final double[] results = new double[10 * 1000 * 1000];
{
    long start = System.nanoTime();
    // using a plain loop.
    for (int i = 0; i < results.length; i++) {
        results[i] = Math.pow(i, 1.5);
    }
    long time = System.nanoTime() - start;
    System.out.printf("With one thread it took %.1f ns per pow 1.5%n", (double) time / results.length);
}
{
    int threads = 4;
    ExecutorService ex = Executors.newFixedThreadPool(threads);
    long start = System.nanoTime();
    int blockSize = results.length / threads;
    // using a plain loop.
    for (int i = 0; i < threads; i++) {
        final int istart = i * blockSize;
        final int iend = (i + 1) * blockSize;
        ex.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = istart; i < iend; i++)
                    results[i] = Math.pow(i, 1.5);
            }
        });
    }
    ex.shutdown();
    ex.awaitTermination(1, TimeUnit.MINUTES);
    long time = System.nanoTime() - start;
    System.out.printf("With four threads it took %.1f ns per pow 1.5%n", (double) time / results.length);
}

печать

With one thread it took 287.6 ns per pow 1.5
With four threads it took 77.3 ns per pow 1.5

Это почти четырехкратное улучшение.

6 голосов
/ 31 мая 2011

Сколько потоков используется в общей сложности? Это, вероятно, источник вашей проблемы. Использование нескольких потоков действительно даст прирост производительности, если:

  • Каждая задача в потоке выполняет своего рода блокировку. Например, ожидание ввода / вывода. Использование нескольких потоков в этом случае позволяет использовать время блокировки другими потоками.
  • или у вас есть несколько ядер. Если у вас 4 ядра или 4 процессора, вы можете выполнять 4 задачи одновременно (или 4 потока).

Звучит так, как будто вы не блокируете темы, поэтому я думаю, что вы используете слишком много потоков. Например, если вы используете 10 разных потоков для одновременной работы, но имеете только 2 ядра, это, вероятно, будет намного медленнее, чем выполнение всех задач в последовательности. Обычно запускайте количество потоков, равное количеству ядер / процессоров. Увеличивайте используемые потоки, медленно измеряя производительность каждый раз. Это даст вам оптимальное количество потоков для использования.

1 голос
/ 01 июня 2011

Скорее всего, вы знаете об этом, но если нет, пожалуйста, прочитайте Закон Амдала .Он дает взаимосвязь между ожидаемым ускорением программы с использованием параллелизма и последовательных сегментов программы.

1 голос
/ 31 мая 2011

Возможно, вы могли бы попытаться реализовать повторную реализацию своего кода, используя fork / join из JDK 7, и посмотреть, что он делает?

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

0 голосов
/ 31 мая 2011

синхронизация между ядрами происходит намного медленнее, чем в одноядерной среде. Посмотрите, можете ли вы ограничить jvm до 1 ядра (см. в этом блоге )

или вы можете использовать ExecuterorService и использовать invokeAll для запуска параллельных задач

...