Я пишу многопоточное Java-приложение, которое работает на процессоре Nehalem. Однако у меня проблема в том, что, начиная с 4 потоков, я почти не вижу ускорения в своем приложении.
Я сделал несколько простых тестов. Я создал поток, который просто выделяет большой массив и делает доступ к случайным записям в массиве. Поэтому, когда я запускаю количество потоков, время работы не должно меняться (при условии, что я не превышаю количество доступных ядер ЦП). Но я заметил, что запуск одного или двух потоков занимает почти одинаковое время, но запуск четырех или восьми потоков значительно медленнее. Поэтому, прежде чем пытаться решить проблему алгоритмизации и синхронизации в моем приложении, я хочу выяснить, какой максимально возможной параллелизации я могу достичь.
Я использовал опцию -XX:+UseNUMA
JVM, поэтому массивы следует размещать в памяти рядом с соответствующими потоками.
P.S. Если потоки выполняли простой математический расчет, то для 4 и даже для 8 потоков времени не было, поэтому я пришел к выводу, что, когда потоки обращаются к памяти, у меня возникают некоторые проблемы.
Любая помощь или идеи приветствуются, спасибо.
EDIT
Спасибо вам всем за ответы. Я вижу, что недостаточно хорошо объяснил себя.
Прежде чем пытаться устранить проблемы с синхронизацией в моем приложении, я сделал простой тест, который проверяет наилучшее возможное распараллеливание, которое может быть достигнуто. Код выглядит следующим образом:
public class TestMultiThreadingArrayAccess {
private final static int arrSize = 40000000;
private class SimpleLoop extends Thread {
public void run() {
int array[] = new int[arrSize];
for (long i = 0; i < arrSize * 10; i++) {
array[(int) ((i * i) % arrSize)]++; // randomize a bit the access to the array
}
long sum = 0;
for (int i = 0; i < arrSize; i++)
sum += array[i];
}
}
public static void main(String[] args) {
TestMultiThreadingArrayAccess test = new TestMultiThreadingArrayAccess();
for (int threadsNumber : new int[] { 1, 2, 4, 8 }) {
Statistics timer = new Statistics("Executing " + threadsNumber+ " threads"); // Statistics is a simple helper class that measures the times
timer.start();
test.doTest(threadsNumber);
timer.stop();
System.out.println(timer.toString());
}
}
public void doTest(int threadsNumber) {
Thread threads[] = new Thread[threadsNumber];
for (int i = 0; i < threads.length; i++) {
threads[i] = new SimpleLoop();
threads[i].start();
}
for (int i = 0; i < threads.length; i++)
try {
threads[i].join();
} catch (InterruptedException e) {
};
}
}
Итак, как вы видите, в этом мини-тесте вообще нет синхронизации, а также выделение массива внутри потока, поэтому его следует поместить в кусок памяти, к которому можно быстро получить доступ. Также в этом коде нет конфликтов памяти. Тем не менее, для 4 потоков время выполнения уменьшается на 30%, а 8 потоков работают в два раза медленнее. Как и вы из кода, я просто жду, пока все потоки завершат свою работу, и поскольку их работа независима, количество потоков не должно влиять на общее время выполнения.
На машине установлены 2 четырехъядерных гиперпоточных процессора Nehalem (всего 16 процессоров), поэтому каждый из 8 потоков может захватывать только свой процессор.
Когда я попытался запустить этот тест с меньшим массивом (20K записей), падение времени выполнения 4 потоков составило 7%, а 8 потоков - 14%, что является удовлетворительным. Но когда я пытаюсь работать со случайным доступом к большому массиву (40M записей), время выполнения резко возрастает, поэтому я думаю, что есть проблема в том, что большие куски памяти (потому что они не помещаются в кеш-память?) Доступны в не эффективный способ.
Есть идеи как это исправить?
Надеюсь, это прояснит вопрос лучше, еще раз спасибо.