Почему два потока вычисляются медленнее, чем один?

Я работаю над тестовым приложением, созданным на Java. Я запускаю этот код дважды в основном потоке, а затем один раз в двух отдельных потоках:

if (testing == null) {
    testing = new byte[TEST_SIZE][TEST_SIZE][TEST_SIZE];
}

for (int x = 0; x < TEST_SIZE; x ++) {
    for (int y = 0; y < TEST_SIZE; y ++) {
        for (int z = 0; z < TEST_SIZE; z ++) {
            testing[x][y][z] = (byte)RANDOM.nextInt(100);
        }
    }
}

if (finished == Test.LOOP_COUNT - 1) {
    testing = null;
}

Задача в основном потоке выполняется намного быстрее, чем два потока, как показано в выходных данных приложения:

Starting test Array Handling with a single core.
Loop #1 finished in 1.820588011 seconds.
Loop #2 finished in 1.779667175 seconds.
Finished in 3 seconds.
Starting test Array Handling with multiple cores.
Loop #2 finished in 9.433253526 seconds.
Loop #1 finished in 9.465652985 seconds.
Finished in 9 seconds.

Я где-то читал, что два потока, выполняющих действительно быстрые операции, не будут работать так же хорошо, как один поток, но два потока, работающих над более сложными операциями, превзошли один поток. Я не думал, что это так, потому что каждый цикл довольно сложный. Единственная причина, по которой я могу думать, это то, что потоки на самом деле не работают на своих собственных ядрах. Может ли это быть проблемой? У меня 2-х ядерный 4-х ниточный Intel Core i7-3537U.

РЕДАКТИРОВАТЬ:

Тестовый класс:

package net.jibini.park.tests;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 *
 * @author zgoethel12
 */
public abstract class Test {

public static final Random RANDOM = new Random();
public static final int LOOP_COUNT = 2;

public static final CopyOnWriteArrayList<Test> tests = new CopyOnWriteArrayList<Test>();

public int finished = 0;
public int longestTime = 0;
public double timeSum = 0;

static {
    RANDOM.setSeed(481923);
    tests.add(new TestArray());
}

public abstract String getName();

public void runTest(final boolean multithread) {

    new Thread(new Runnable() {
            @Override
            public void run() {
                finished = 0;
                longestTime = 0;
                timeSum = 0;

                if (multithread) {
                    for (int i = 0; i < LOOP_COUNT; i ++) {
                        final int f = i;
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                doLoop(f + 1);
                                Thread.currentThread().interrupt();
                            }
                        }).start();
                    }
                } else {
                    for (int i = 0; i < LOOP_COUNT; i ++) {
                        doLoop(i + 1);
                    }
                }

                while (finished < LOOP_COUNT) {
                    System.out.print("");
                }

                System.out.println("Finished in " + (multithread ? longestTime : (int)timeSum) + " seconds.");
                Thread.currentThread().interrupt();
            }
    }).start();

}

public void doLoop(int id) {

    long start = System.nanoTime();
    doTest(id);
    handleLoopFinish(id, start);

}

public abstract void doTest(int id);

public void handleLoopFinish(int id, long start) {

    long current = System.nanoTime();
    long difference = current - start;
    double seconds = (double)difference / 1000000000;
    if (seconds > longestTime) {
        longestTime = (int)seconds;
    }
    timeSum += seconds;
    System.out.println("Loop #" + id + " finished in " + seconds + " seconds.");
    finished ++;

}

}

Тест массива:

package net.jibini.park.tests;

/**
 *
 * @author zgoethel12
 */
public class TestArray extends Test {

public static final int TEST_SIZE = 512;

byte[][][] testing = null;

@Override
public void doTest(int id) {

    if (testing == null) {
        testing = new byte[TEST_SIZE][TEST_SIZE][TEST_SIZE];
    }

    for (int x = 0; x < TEST_SIZE; x ++) {
        for (int y = 0; y < TEST_SIZE; y ++) {
            for (int z = 0; z < TEST_SIZE; z ++) {
                testing[x][y][z] = (byte)RANDOM.nextInt(100);
            }
        }
    }

    if (finished == Test.LOOP_COUNT - 1) {
        testing = null;
    }

}

@Override
public String getName() {
    return "Array Handling";
}

}

2 ответа

Решение

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

Попробуй использовать ThreadLocalRandom,

Запуск второго потока, в зависимости от того, какую библиотеку вы выбираете для выполнения многопоточности, занимает много времени, а выполняемая вами операция, в зависимости от размера TEST_SIZE, вероятно, очень эффективно выполняется одним процессором, потому что вы проследить массив в памяти непрерывным способом.

В ходе курсовых работ меня проинструктировали, что встроенная библиотека Thread Java имеет гораздо больше накладных расходов, чем, скажем, инфраструктура ForkJoin, поэтому, возможно, вы получите более ожидаемые результаты, используя эту библиотеку.

В качестве ресурса вот хороший сайт, который я использовал в своем классе параллелизма: http://homes.cs.washington.edu/~djg/teachingMaterials/spac/grossmanSPAC_forkJoinFramework.html

Будьте осторожны, чтобы выполнить прогрев, как сказано ниже. Они имеют решающее значение, чтобы убедиться, что вы можете увидеть преимущества. Мы попытались сделать ~100 прогонов, с ~ 10 прогревом, чтобы убедиться, что вы получите хорошие средние значения. Усреднение по многим прогонам очень помогает из-за переключения контекста / других компьютерных процессов, которые могут добавить изменчивость в ваши испытания!

Другие вопросы по тегам