Вы создаете много потоков. Не только дорого стоит создавать потоки, но и для приложения с привязкой к процессору, вам не нужно больше потоков, чем у вас есть доступные процессоры (если вы это делаете, вам нужно потратить переключение мощности между потоками, что также может вызвать кеш промахи, которые очень дорогие).
Также нет необходимости отправлять темы на execute
; все, что ему нужно, - это Runnable
. Вы получите большой прирост производительности, применяя эти изменения:
сделать ExecutorService
статический элемент, размер его для текущего процессора, и отправить его ThreadFactory
поэтому не хранит программу работы после того, как main
закончил. (Это, вероятно, будет архитектурно уборщик, чтобы отправить его в качестве параметра метода, а не держать его в статическом поле, я оставляю это в качестве упражнения для читателя ☺.)
private static final ExecutorService workerPool =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
Сделать MatrixThread
реализовать Runnable
довольно чем наследовать Thread
. Потоки дороги для создания; POJO очень дешевы. Вы также можете сделать это static
, что делает экземпляры меньше (поскольку нестатические классы получают неявную ссылку на окружающий объект).
private static class MatrixThread implements Runnable
От изменения (1), вы можете больше не awaitTermination
, чтобы убедиться, что все задачи будут завершены (как этот работник бассейна).Вместо этого используйте метод submit
, который возвращает Future<?>
. Соберите все будущие объекты в списке, и когда вы отправите все задания, выполните перебор по списку и вызовите get
для каждого объекта.
Вашего метод multiply
должен выглядеть примерно так:
public Matrix multiply(Matrix multiplier) throws InterruptedException {
Matrix result = new Matrix(dimension);
List<Future<?>> futures = new ArrayList<Future<?>>();
for(int currRow = 0; currRow < multiplier.dimension; currRow++) {
for(int currCol = 0; currCol < multiplier.dimension; currCol++) {
Runnable worker = new MatrixThread(this, multiplier, currRow, currCol, result);
futures.add(workerPool.submit(worker));
}
}
for (Future<?> f : futures) {
try {
f.get();
} catch (ExecutionException e){
throw new RuntimeException(e); // shouldn't happen, but might do
}
}
return result;
}
это будет быстрее, чем однопоточная версия? Ну, на моем, возможно, дерьмовом боксе, многопоточная версия медленнее для значений n
< 1024.
Это просто царапает поверхность. реальная проблема заключается в том, что вы создаете МНОГО из MatrixThread
экземпляров - ваше потребление памяти O(n²)
, который является очень плохой знак. Перемещение внутреннего цикла на MatrixThread.run
повысило бы производительность в craploads (в идеале вы не создаете больше задач, чем рабочие потоки).
Edit: Поскольку у меня есть более важные вещи, чтобы сделать, я не мог сопротивляться оптимизации этого дальше. Я пришел с этим (... чудовищно уродливый кусок кода), что «только» создает O(n)
работы:
public Matrix multiply(Matrix multiplier) throws InterruptedException {
Matrix result = new Matrix(dimension);
List<Future<?>> futures = new ArrayList<Future<?>>();
for(int currRow = 0; currRow < multiplier.dimension; currRow++) {
Runnable worker = new MatrixThread2(this, multiplier, currRow, result);
futures.add(workerPool.submit(worker));
}
for (Future<?> f : futures) {
try {
f.get();
} catch (ExecutionException e){
throw new RuntimeException(e); // shouldn't happen, but might do
}
}
return result;
}
private static class MatrixThread2 implements Runnable
{
private Matrix self, mul, result;
private int row, col;
private MatrixThread2(Matrix a, Matrix b, int row, Matrix result)
{
this.self = a;
this.mul = b;
this.row = row;
this.result = result;
}
@Override
public void run()
{
for(int col = 0; col < mul.dimension; col++) {
int cellResult = 0;
for (int i = 0; i < self.getMatrixDimension(); i++)
cellResult += self.template[row][i] * mul.template[i][col];
result.template[row][col] = cellResult;
}
}
}
Это еще не большой, но в основном многопоточная версия может вычислить, что вы будете терпеливы достаточно ждать, и он будет делать это быстрее, чем однопоточная версия.
Ваш код пропускает метод «Умножить» –
Зачем вам многопоточность? Это полностью привязано к процессору, не похоже, что поток заблокирован, ожидая ввода-вывода. –
Многопоточность может работать нормально, но будет больше зависеть от того, сколько процессоров (10x10 умножить на 10x10 создает 100 потоков в вашем примере ... скорее всего, только 2-8 cpus) и насколько велики матрицы (они вписываются в L2/L3?). Родные библиотеки, такие как MKL и OpenCL, намного лучше справляются с этим. – basszero