shurygin_dima_lab_1 is ready

This commit is contained in:
2025-03-14 23:15:18 +04:00
parent 373ad2c379
commit b77fe9893f
2 changed files with 361 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class DividingByAverage {
public static void main(String[] args) {
Random random = new Random();
// Создание массива
double[][] array = new double[8000][8000];
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
array[i][j] = random.nextInt(101);
}
}
// Создание копии исходного массива
double[][] arraySave = new double[array.length][array.length];
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
arraySave[i][j] = array[i][j];
}
}
// Тест однопоточного алгоритма
System.out.println("Однопоточный алгоритм:");
long startTime1 = System.nanoTime();
singleThreadedAlgorithm(array);
long endTime1 = System.nanoTime();
long duration1 = endTime1 - startTime1;
System.out.println("Время выполнения: " + (duration1 / 1000000) + " мс");
System.out.println(" ");
// Восстановление массива
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
array[i][j] = arraySave[i][j];
}
}
// Тест многопоточного алгоритма с ThreadPoolExecutor
System.out.println("Алгоритм с ThreadPoolExecutor:");
long startTime2 = System.nanoTime();
threadPoolExecutorAlgorithm(array);
long endTime2 = System.nanoTime();
long duration2 = endTime2 - startTime2;
System.out.println("Время выполнения: " + (duration2 / 1000000) + " мс");
System.out.println(" ");
// Восстановление массива
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
array[i][j] = arraySave[i][j];
}
}
// Тест многопоточного алгоритма с ForkJoinPool
System.out.println("Алгоритм с ForkJoinPool:");
long startTime3 = System.nanoTime();
forkJoinPoolAlgorithm(array);
long endTime3 = System.nanoTime();
long duration3 = endTime3 - startTime3;
System.out.println("Время выполнения: " + (duration3 / 1000000) + " мс");
System.out.println(" ");
// Восстановление массива
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
array[i][j] = arraySave[i][j];
}
}
}
// Однопоточный алгоритм
public static void singleThreadedAlgorithm(double[][] array) {
// Сумма всех элементов массива
double sum = 0;
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
sum += array[i][j];
}
}
// Среднее арифметическое
double average = (double) sum / (array.length * array.length);
System.out.println("array[100][100] = " + array[100][100]);
System.out.println("Среднее арифметическое: " + average);
// Деление элементов массива на среднее
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array.length; j++) {
array[i][j] = array[i][j] / average;
}
}
System.out.println("array[100][100] = " + array[100][100]);
}
// Тест многопоточного алгоритма с ThreadPoolExecutor
public static void threadPoolExecutorAlgorithm(double[][] array) {
int size = array.length;
// Количество потоков
int threads = 4;
// Создаем пул потоков
ExecutorService executor = Executors.newFixedThreadPool(threads);
// Используем AtomicLong для безопасного накопления суммы в потоках
AtomicLong totalSum = new AtomicLong(0);
// Считаем сумму элементов массива с использованием потоков
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 localSum = 0;
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < size; j++) {
localSum += array[i][j];
}
}
// Добавляем локальную сумму в атомарную переменную.
// Преобразуем в long для атомарного сложения
totalSum.addAndGet((long) localSum);
});
}
// Делим элементы массива на среднее значение
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++) {
array[i][j] /= totalSum.get() / (double) (size * size);
}
}
});
}
// Закрываем пул после того, как все задачи были отправлены
executor.shutdown();
// Ожидаем завершения всех задач
while (!executor.isTerminated()) {
// Ждем завершения всех задач
}
double average = totalSum.get() / (double) (size * size);
System.out.println("Среднее арифметическое: " + average);
System.out.println("array[100][100] = " + array[100][100]);
}
// Порог разбиения
private static final int THRESHOLD = 50000;
// Многопоточный алгоритм с ForkJoinPool
public static void forkJoinPoolAlgorithm(double[][] array) {
int size = array.length;
ForkJoinPool forkJoinPool = new ForkJoinPool();
// Подсчет суммы
SumTask sumTask = new SumTask(array, 0, size);
double totalSum = forkJoinPool.invoke(sumTask);
// Вычисление среднего
double average = totalSum / (size * size);
System.out.println("Среднее арифметическое: " + average);
// Деление массива
forkJoinPool.invoke(new NormalizeTask(array, 0, size, average));
forkJoinPool.shutdown();
System.out.println("array[100][100] = " + array[100][100]);
}
// Подсчет суммы
static class SumTask extends RecursiveTask<Double> {
private final double[][] array;
private final int startRow, endRow;
SumTask(double[][] array, int startRow, int endRow) {
this.array = array;
this.startRow = startRow;
this.endRow = endRow;
}
@Override
protected Double compute() {
// Если строк меньше порога, считаем напрямую
if (endRow - startRow <= THRESHOLD) {
double sum = 0;
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < array[i].length; j++) {
sum += array[i][j];
}
}
return sum;
} else {
// Разделяем массив на две части
int midRow = (startRow + endRow) / 2;
SumTask top = new SumTask(array, startRow, midRow);
SumTask bottom = new SumTask(array, midRow, endRow);
// Запускаем верхнюю часть в новом потоке
top.fork();
// Вычисляем нижнюю часть в текущем потоке
double bottomResult = bottom.compute();
// Ждём завершения верхней части и суммируем
return top.join() + bottomResult;
}
}
}
// Деление массива
static class NormalizeTask extends RecursiveAction {
private final double[][] array;
private final int startRow, endRow;
private final double average;
NormalizeTask(double[][] array, int startRow, int endRow, double average) {
this.array = array;
this.startRow = startRow;
this.endRow = endRow;
this.average = average;
}
@Override
protected void compute() {
if (endRow - startRow <= THRESHOLD) {
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < array[i].length; j++) {
array[i][j] /= average;
}
}
} else {
int midRow = (startRow + endRow) / 2;
invokeAll(new NormalizeTask(array, startRow, midRow, average),
new NormalizeTask(array, midRow, endRow, average));
}
}
}
}

View File

@@ -0,0 +1,112 @@
# Лабораторная №1
Разработка многопоточного приложения с использованием Java Concurrency
согласно варианту задания.
Необходимо:
1) Разработать однопоточный вариант алгоритма и замерить время его работы.
2) Разработать параллельный вариант алгоритма с использованием
ThreadPoolExecutor и замерить время его работы
3) Разработать параллельный вариант алгоритма с использованием ForkJoinPoll
и замерить время его работы.
Массив генерируется до работы всех вариантов алгоритмов. Все три
алгоритма обрабатывают три одинаковых массива.
## Вариант и задание
3) Разделить элементы матрицы на среднее арифметическое всех ее элементов.
### Описание
Данная программа разработана для сравнения производительности однопоточного и многопоточного подходов к делению элементов матрицы на среднее арифметическое её элементов. Задача состоит из двух основных частей:
1. Вычисление среднего арифметического
2. Деление элементов матрицы на среднее арифметическое
Эти этапы выполняются с разными вариантами многопоточности, чтобы выявить их влияние на производительность.
### Как запустить
```sh
javac DividingByAverage.java
java DividingByAverage
```
### Используемые технологии
Программа использует Java Concurrency, включая классы и интерфейсы:
1) `ExecutorService`
2) `ThreadPoolExecutor`
3) `ForkJoinPool`
4) `RecursiveAction`
### Что делает программа
1. Генерирует матрицу заданного размера, заполненную случайными значениями.
2. Вычисляет среднее арифметическое элементов матрицы.
3. Делит все элементы матрицы на среднее арифметическое.
4. Замеряет время выполнения для однопоточного и двух многопоточных методов.
## Результаты тестов
### Для матрцы 5000*5000:
```
Однопоточный алгоритм:
array[100][100] = 15.0
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 59 мс
Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 56 мс
Алгоритм с ForkJoinPool:
Среднее арифметическое: 50.00305776
array[100][100] = 0.29998165456191894
Время выполнения: 66 мс
```
### Для матрцы 10000*10000:
```
Однопоточный алгоритм:
array[100][100] = 38.0
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 186 мс
Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 140 мс
Алгоритм с ForkJoinPool:
Среднее арифметическое: 50.00220663
array[100][100] = 0.7599664607041763
Время выполнения: 174 мс
```
### Для матрцы 15000*15000:
```
Однопоточный алгоритм:
array[100][100] = 4.0
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 388 мс
Алгоритм с ThreadPoolExecutor:
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 323 мс
Алгоритм с ForkJoinPool:
Среднее арифметическое: 49.99820225777778
array[100][100] = 0.08000287649097934
Время выполнения: 379 мс
```
## Вывод ##
Пока матрицы небольших размеров, можно заметить, что многопоточные алгоритмы могут не иметь преимущества по времени выполнения перед однопоточным алгоритмом или даже быть хуже.
Это происходит из-за того, что ThreadPool и ForkJoin затрачивают дополнительные ресурсы на создание и поддержание потоков.
По мере роста размеров матрицы, многопоточные алгоритмы показывают большую эффективность, чем однопоточный. Однопоточный алгоритм начинает работать медленнее из-за большого количества элементов.
ThreadPool и ForkJoin начинают работать быстрее, так как дополнительные траты на поддержание потоков с лихвой компенсируются эффективностью многопоточного метода решения задачи.