Merge pull request 'ПИбд-22_Хасянов_А.Н._Лаб_1' (#30) from Aidar/SSPR_25:khasyanov_aidar_lab_1 into main

Неплохо бы добавить описание кода, в остальном хорошо
Reviewed-on: #30
This commit was merged in pull request #30.
This commit is contained in:
2025-03-15 00:23:22 +04:00
2 changed files with 334 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class MinDivisionTask {
static double[][] mainArray = new double[4000][4000];
static double[][] bufferArray = new double[mainArray.length][mainArray.length];
static final int FRONTIER = 5000; // Порог разбиения
static final int THREADS = 2; // Кол-во потоков
static int countOftests = 20; // Кол-во тестов
public static void main(String[] args) {
DoTests();
}
public static void DoTests() {
long SingleThreadedTimeSum = 0;
long ThreadPoolExecutorTimeSum = 0;
long ForkJoinPoolTimeSum = 0;
int count = 0;
Random random = new Random();
while (count != countOftests) {
count += 1;
for (int i = 0; i < mainArray.length; i++) {
for (int j = 0; j < mainArray.length; j++) {
mainArray[i][j] = random.nextInt(-100,100);
bufferArray[i][j] = mainArray[i][j];
}
}
//PrintLogMatrix(mainArray); System.out.println();
// Однопоточный алгоритм
long SingleThreadedTime = ComputeAndPrintDuration("SingleThreadedAlgorithm");
SingleThreadedTimeSum += SingleThreadedTime;
// Многопоточный алгоритм
long ThreadPoolExecutorTime = ComputeAndPrintDuration("ThreadPoolExecutorAlgorithm");
ThreadPoolExecutorTimeSum += ThreadPoolExecutorTime;
// ForkJoinPool-алгоритм
long ForkJoinPoolTime = ComputeAndPrintDuration("ForkJoinPoolAlgorithm");
ForkJoinPoolTimeSum += ForkJoinPoolTime;
System.out.println("{Тест "+ count + "} " + "SingleThreadedTime: " + SingleThreadedTime +
" ThreadPoolExecutorTime: " + ThreadPoolExecutorTime + " ForkJoinPoolTime: " + ForkJoinPoolTime);
}
System.out.println("SingleThreadedTime: " + SingleThreadedTimeSum/countOftests);
System.out.println("ThreadPoolExecutorTime: " + ThreadPoolExecutorTimeSum/countOftests);
System.out.println("ForkJoinPoolTime: " + ForkJoinPoolTimeSum/countOftests);
}
public static void CopyArray() {
for (int i = 0; i < mainArray.length; i++) {
for (int j = 0; j < mainArray.length; j++) mainArray[i][j] = bufferArray[i][j];
}
}
public static void PrintLogMatrix(double[][] mainArray) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.print(mainArray[i][j] + " ");
}
System.out.println();
}
}
public static long ComputeAndPrintDuration(String typeOfAlgoritm) {
long startTime = 0, endTime = 0, duration;
switch (typeOfAlgoritm) {
case "SingleThreadedAlgorithm":
startTime = System.nanoTime();
SingleThreadedAlgorithm(mainArray);
endTime = System.nanoTime();
break;
case "ThreadPoolExecutorAlgorithm":
startTime = System.nanoTime();
ThreadPoolExecutorAlgorithm(mainArray);
endTime = System.nanoTime();
break;
case "ForkJoinPoolAlgorithm":
startTime = System.nanoTime();
ForkJoinPoolAlgorithm(mainArray);
endTime = System.nanoTime();
break;
default:
break;
}
duration = (endTime - startTime) / 1000000;
//System.out.println("Время выполнения: " + (duration) + " мс\n");
CopyArray();
return duration;
}
public static void SingleThreadedAlgorithm(double[][] mainArray) {
//System.out.println("Однопоточный алгоритм");
double min = Double.MAX_VALUE;
for (int i = 0; i < mainArray.length; i++) {
for (int j = 0; j < mainArray.length; j++) {
if(min > mainArray[i][j]) min = mainArray[i][j];
}
}
//System.out.println("Наименьший элемент: " + min);
for (int i = 0; i < mainArray.length; i++) {
for (int j = 0; j < mainArray.length; j++) {
mainArray[i][j] = mainArray[i][j] / min;
}
}
//PrintLogMatrix(mainArray);
}
public static void ThreadPoolExecutorAlgorithm(double[][] mainArray) {
//System.out.println("ThreadPoolExecutorAlgorithm");
int size = mainArray.length;
ExecutorService executor = Executors.newFixedThreadPool(THREADS); // Создаем пул потоков
AtomicLong globalMin = new AtomicLong(Long.MAX_VALUE);
// Вычисление суммы элементов массива
for (int t = 0; t < THREADS; t++) {
final int startRow = t * (size / THREADS); // Начало данного потока
final int endRow = (t == THREADS - 1) ? size : (t + 1) * (size / THREADS); // Конец данного потока
executor.submit(() -> {
double localMin = Double.MAX_VALUE;
// Вычисление суммы в рамках выделенного потоку диапазона строк
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < size; j++) {
if(localMin > mainArray[i][j]) localMin = mainArray[i][j];
}
}
// Вычисление наименьшего элемента глобально
long finalLocalMin = (long)localMin;
globalMin.updateAndGet(v -> Math.min(v, finalLocalMin));
});
}
// Деление элементов массива на наименьшее значение
for (int t = 0; t < THREADS; t++) {
final int startRow = t * (size / THREADS); // Начало данного потока
final int endRow = (t == THREADS - 1) ? size : (t + 1) * (size / THREADS); // Конец данного потока
executor.submit(() -> {
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < size; j++) {
mainArray[i][j] /= globalMin.get();
}
}
});
}
executor.shutdown();
while (!executor.isTerminated()) { /* Ожидание завершения всех задач */ }
double min = globalMin.get();
//System.out.println("Наименьший элемент: " + min);
//PrintLogMatrix(mainArray);
}
public static void ForkJoinPoolAlgorithm(double[][] mainArray) {
//System.out.println("ForkJoinPoolAlgorithm");
int size = mainArray.length;
ForkJoinPool forkJoinPool = new ForkJoinPool();
// Вычисление наименьшего элемента
MinTask minTask = new MinTask(mainArray, 0, size);
double min = forkJoinPool.invoke(minTask);
//System.out.println("Наименьший элемент: " + min);
// Деление массива на наименьший элемент
forkJoinPool.invoke(new DivisionTask(mainArray, 0, size, min));
forkJoinPool.shutdown();
//PrintLogMatrix(mainArray);
}
static class MinTask extends RecursiveTask<Double> {
private final double[][] mainArray;
private final int startRow, endRow;
MinTask(double[][] mainArray, int startRow, int endRow) {
this.mainArray = mainArray;
this.startRow = startRow;
this.endRow = endRow;
}
@Override
protected Double compute() {
if (endRow - startRow <= FRONTIER) {
double min = Double.MAX_VALUE;
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < mainArray[i].length; j++) {
if(min > mainArray[i][j]) min = mainArray[i][j];
}
}
return (Double) min;
} else {
int midRow = (startRow + endRow) / 2;
MinTask top = new MinTask(mainArray, startRow, midRow);
MinTask bottom = new MinTask(mainArray, midRow, endRow);
top.fork();
double bottomResult = bottom.compute();
return (Double) Math.min(top.join(), (long)bottomResult);
}
}
}
static class DivisionTask extends RecursiveAction {
private final double[][] mainArray;
private final int startRow, endRow;
private final double min;
DivisionTask(double[][] mainArray, int startRow, int endRow, double min) {
this.mainArray = mainArray;
this.startRow = startRow;
this.endRow = endRow;
this.min = min;
}
@Override
protected void compute() {
if (endRow - startRow <= FRONTIER) {
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < mainArray[i].length; j++) {
mainArray[i][j] /= min;
}
}
} else {
int midRow = (startRow + endRow) / 2;
invokeAll(new DivisionTask(mainArray, startRow, midRow, min),
new DivisionTask(mainArray, midRow, endRow, min));
}
}
}
}

View File

@@ -0,0 +1,107 @@
# Лабораторная №1
Разработка многопоточного приложения с использованием Java Concurrency
согласно варианту задания.
Необходимо:
1) Разработать однопоточный вариант алгоритма и замерить время его работы.
2) Разработать параллельный вариант алгоритма с использованием
ThreadPoolExecutor и замерить время его работы
3) Разработать параллельный вариант алгоритма с использованием ForkJoinPoll
и замерить время его работы.
Массив генерируется до работы всех вариантов алгоритмов. Все три
алгоритма обрабатывают три одинаковых массива.
## Вариант и задание
2. Разделить элементы матрицы на наименьший элемент.
### Описание
Данная программа разработана для сравнения производительности однопоточного и многопоточного подходов к делению
элементов матрицы на среднее арифметическое её элементов. Задача состоит из двух основных частей:
1. Нахождение наименьшего элемента в матрице
2. Деление элементов матрицы на наименьший элемент в матрице
Эти этапы выполняются с разными вариантами многопоточности, чтобы выявить их влияние на производительность.
### Как запустить
java MinDivisionTask
### Используемые технологии
Программа использует Java Concurrency, включая классы и интерфейсы:
1) ExecutorService
2) RecursiveAction
3) ForkJoinPool
ThreadPoolExecutor был реализован вручную
### Что делает программа
1. Генерирует матрицу заданного размера, заполненную случайными значениями.
2. Находит наименьший элемент в матрице.
3. Делит все элементы матрицы на наименьший элемент в матрице.
4. Замеряет время выполнения для однопоточного и двух многопоточных (ThreadPoolExecutor, ForkJoinPool) алгоритмов.
## Результаты тестов
(SingleThreadedTime - Однопоточный алгоритм)
### Для матрцы 4000*4000:
{Тест 1} SingleThreadedTime: 80 ThreadPoolExecutorTime: 78 ForkJoinPoolTime: 64
{Тест 2} SingleThreadedTime: 45 ThreadPoolExecutorTime: 39 ForkJoinPoolTime: 64
{Тест 3} SingleThreadedTime: 37 ThreadPoolExecutorTime: 31 ForkJoinPoolTime: 32
{Тест 4} SingleThreadedTime: 32 ThreadPoolExecutorTime: 40 ForkJoinPoolTime: 46
{Тест 5} SingleThreadedTime: 43 ThreadPoolExecutorTime: 32 ForkJoinPoolTime: 50
{Тест 6} SingleThreadedTime: 44 ThreadPoolExecutorTime: 29 ForkJoinPoolTime: 45
{Тест 7} SingleThreadedTime: 45 ThreadPoolExecutorTime: 31 ForkJoinPoolTime: 45
{Тест 8} SingleThreadedTime: 44 ThreadPoolExecutorTime: 31 ForkJoinPoolTime: 47
{Тест 9} SingleThreadedTime: 43 ThreadPoolExecutorTime: 31 ForkJoinPoolTime: 45
{Тест 10} SingleThreadedTime: 43 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 11} SingleThreadedTime: 34 ThreadPoolExecutorTime: 31 ForkJoinPoolTime: 32
{Тест 12} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 13} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 31
{Тест 14} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 15} SingleThreadedTime: 32 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 16} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 17} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 32
{Тест 18} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 31
{Тест 19} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 31
{Тест 20} SingleThreadedTime: 31 ThreadPoolExecutorTime: 30 ForkJoinPoolTime: 31
Среднее время:
SingleThreadedTime: 38
ThreadPoolExecutorTime: 33
ForkJoinPoolTime: 39
### Для матрцы 11000*11000:
{Тест 1} SingleThreadedTime: 402 ThreadPoolExecutorTime: 259 ForkJoinPoolTime: 271
{Тест 2} SingleThreadedTime: 406 ThreadPoolExecutorTime: 273 ForkJoinPoolTime: 236
{Тест 3} SingleThreadedTime: 388 ThreadPoolExecutorTime: 245 ForkJoinPoolTime: 226
{Тест 4} SingleThreadedTime: 237 ThreadPoolExecutorTime: 329 ForkJoinPoolTime: 339
{Тест 5} SingleThreadedTime: 333 ThreadPoolExecutorTime: 251 ForkJoinPoolTime: 227
{Тест 6} SingleThreadedTime: 330 ThreadPoolExecutorTime: 231 ForkJoinPoolTime: 220
{Тест 7} SingleThreadedTime: 344 ThreadPoolExecutorTime: 234 ForkJoinPoolTime: 224
{Тест 8} SingleThreadedTime: 330 ThreadPoolExecutorTime: 224 ForkJoinPoolTime: 224
{Тест 9} SingleThreadedTime: 329 ThreadPoolExecutorTime: 222 ForkJoinPoolTime: 220
{Тест 10} SingleThreadedTime: 336 ThreadPoolExecutorTime: 230 ForkJoinPoolTime: 239
{Тест 11} SingleThreadedTime: 238 ThreadPoolExecutorTime: 223 ForkJoinPoolTime: 222
{Тест 12} SingleThreadedTime: 238 ThreadPoolExecutorTime: 223 ForkJoinPoolTime: 234
{Тест 13} SingleThreadedTime: 243 ThreadPoolExecutorTime: 241 ForkJoinPoolTime: 223
{Тест 14} SingleThreadedTime: 253 ThreadPoolExecutorTime: 223 ForkJoinPoolTime: 224
{Тест 15} SingleThreadedTime: 235 ThreadPoolExecutorTime: 223 ForkJoinPoolTime: 223
{Тест 16} SingleThreadedTime: 243 ThreadPoolExecutorTime: 222 ForkJoinPoolTime: 223
{Тест 17} SingleThreadedTime: 236 ThreadPoolExecutorTime: 221 ForkJoinPoolTime: 221
{Тест 18} SingleThreadedTime: 234 ThreadPoolExecutorTime: 225 ForkJoinPoolTime: 233
{Тест 19} SingleThreadedTime: 238 ThreadPoolExecutorTime: 221 ForkJoinPoolTime: 221
{Тест 20} SingleThreadedTime: 241 ThreadPoolExecutorTime: 223 ForkJoinPoolTime: 225
Среднее время:
SingleThreadedTime: 291
ThreadPoolExecutorTime: 237
ForkJoinPoolTime: 233
## Вывод
В ходе анализа производительности алгоритмов обработки матриц разного размера выяснилось, что многопоточные алгоритмы
не всегда оказываются быстрее однопоточных, особенно при работе с небольшими матрицами. В таких случаях многопоточные
алгоритмы, использующие ThreadPoolExecutor и ForkJoinPool, показывают более плохие результаты из-за дополнительных
затрат на создание потоков и управление ими.
Но когда размеры матриц увеличиваются, многопоточные алгоритмы начинают показывать гораздо лучшую производительность
по сравнению с однопоточным. Однопоточный алгоритм начинает замедляться из-за большого объема данных, тогда как
многопоточные алгоритмы, начинают работать быстрее. Это происходит потому, что затраты на управление потоками
оправдываются их параллельной работой, что позволяет значительно ускорить обработку данных.
Вообщем, многопоточность действительно может значительно повысить производительность при обработке больших объемов
данных.