Merge pull request 'PIbd-22_PotantsevD.P._LabWork2' (#72) from denisp222/SSPR_25:potantsev_denis_lab_2 into main

Reviewed-on: sevastyan_b/SSPR_25#72
This commit is contained in:
2025-03-28 23:08:12 +04:00
2 changed files with 225 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
import mpi.MPI;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
public class MatrixSorter {
public static void main(String[] args) throws Exception {
MPI.Init(args);
int rank = MPI.COMM_WORLD.Rank();
int size = MPI.COMM_WORLD.Size();
String hostInfo = getHostName();
System.out.println("Node " + rank + " executing on " + hostInfo);
int rows = 1000;
int cols = 1000;
int[][] matrix = new int[rows][cols];
int chunkSize = rows / (size - 1);
if (rank == 0) {
Random random = new Random();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = random.nextInt(1000);
}
}
long startTime = System.nanoTime();
for (int i = 1; i < size; i++) {
for (int j = 0; j < chunkSize; j++) {
MPI.COMM_WORLD.Send(matrix[(i - 1) * chunkSize + j], 0, cols, MPI.INT, i, j);
}
}
for (int i = 1; i < size; i++) {
for (int j = 0; j < chunkSize; j++) {
MPI.COMM_WORLD.Recv(matrix[(i - 1) * chunkSize + j], 0, cols, MPI.INT, i, j);
}
}
long endTime = System.nanoTime();
System.out.println("Sorted matrix (first 10 rows, first 10 elements each) with row sums:");
printMatrix(matrix, 10);
System.out.println("Sorting time: " + (endTime - startTime) / 1_000_000 + " ms");
} else {
int[][] subMatrix = new int[chunkSize][cols];
for (int i = 0; i < chunkSize; i++) {
MPI.COMM_WORLD.Recv(subMatrix[i], 0, cols, MPI.INT, 0, i);
}
Arrays.sort(subMatrix, Comparator.comparingInt(MatrixSorter::rowSum));
for (int i = 0; i < chunkSize; i++) {
MPI.COMM_WORLD.Send(subMatrix[i], 0, cols, MPI.INT, 0, i);
}
}
MPI.Finalize();
}
private static int rowSum(int[] row) {
return Arrays.stream(row).sum();
}
private static void printMatrix(int[][] matrix, int rowsToShow) {
for (int i = 0; i < Math.min(rowsToShow, matrix.length); i++) {
int sum = rowSum(matrix[i]);
System.out.println("Sum: " + sum + " -> " + Arrays.toString(Arrays.copyOf(matrix[i], 10))); // Ограничение вывода 10 элементами
}
}
private static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return "Unknown Host";
}
}
}

View File

@@ -0,0 +1,144 @@
# Лабораторная работа №2
## Задание
Разработка параллельного MPI приложения на языке Java.
**Необходимо:** разработать параллельный вариант алгоритма с применением MPI и
замерить время его работы. В рамках работы программы должно быть две копии
приложения, которые соединяются друг с другом по сети. Сообщения о статусе
соединения (например, что соединение установлено) должны выводиться в консоль.
Запуск каждого экземпляра MPI происходит в своём LXC контейнере.
**Вариант:** Упорядочить строки матрицы по возрастанию суммы их элементов (**18**).
---
## Описание
Данный проект реализует распределённую сортировку строк матрицы по возрастанию суммы их элементов с использованием `MPI` (Message Passing Interface) и библиотеки `MPJ Express`.
Код запускается на нескольких узлах кластера (или контейнерах **LXC**) и использует механизм передачи данных через *MPI Send/Recv*, что позволяет параллельно сортировать большие матрицы.
### Технологии
- **Java 21** — язык программирования.
- **MPJ Express** библиотека для работы с MPI в Java.
- **LXC (Linux Containers)** для имитации распределённой среды.
- **SSH (Secure Shell)** для аутентификации между узлами.
---
## Как запустить программу
### 1. Установка зависимостей
Перед запуском программы нужно установить нужные пакеты:
- Java и JDK 21 (проверьте javac -version)
```bash
apt install openjdk-21-jdk
```
- MPJ Express
```bash
wget https://sourceforge.net/projects/mpjexpress/files/releases/mpj-v0_44.zip/download
unzip mpj-v0_44.zip
```
Также можно сразу добавить MPJ Express в Path.
### 2. Создание SSH-пользователя
MPJ Express (и другие реализации MPI) используют SSH для запуска процессов на удалённых узлах. Для коммуникации между двумя контейнерами по-хорошему нужно создать беспарольного ssh-пользователя.
```bash
ssh-keygen -t rsa -b 4096
ssh-copy-id sshuser@CT118
```
### 3. Компиляция кода
В папке проекта должен лежать java-класс. Используем *javac* и библиотеку ```MPI``` для компиляции.
```bash
javac -cp $MPJ_HOME/lib/mpj.jar Main.java
```
### 4. Запуск программы
Запускаем daemon-ы на обоих контейнерах и вводим команду для запуска самого проекта.
```bash
mpjdaemon
mpjrun.sh -np 2 -cp . Main
```
Где -np 2 число процессов (включая главный).
***P.S. Предварительно, перед запуском, нужно настроить файл /etc/hosts, где нужно указать ip второго контейнера.***
```
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# --- BEGIN PVE ---
192.168.17.117 CT117.8.8.8.8 CT117
192.168.17.118 CT118
# --- END PVE ---
```
---
## Как работает программа
1. Генерируется случайная матрица размера *1000×1000*.
2. MPI-узлы обмениваются фрагментами матрицы:
- Главный процесс (rank = 0) делит строки между рабочими узлами (rank = 1, 2, ...).
- Каждый узел получает свою часть строк (половину) и сортирует их по сумме элементов.
- Отправляет результат обратно главному процессу.
3. Вывод результатов:
- Выводятся первые 10 строк матрицы с их суммой (для удобства проверки).
- Выводится время выполнения сортировки.
### Ключевые методы
1. **main(String[] args)**
- Инициализирует **MPI** (`MPI.Init(args)`).
- Определяет ранг процесса (*rank*) и общее количество (*size*).
- **Rank 0** (главный узел):
- Генерирует *1000×1000* случайную матрицу.
- Делит матрицу на части и отправляет их рабочим узлам (`Send`).
- Принимает отсортированные данные (`Recv`).
- Выводит итоговую матрицу и время выполнения.
- **Rank > 0** (рабочие узлы):
- Получает свою часть матрицы (`Recv`).
- Сортирует строки по сумме элементов.
- Отправляет данные обратно (`Send`).
2. **rowSum(int[] row)**: возвращает сумму элементов строки.
3. **printMatrix(int[][] matrix, int rowsToShow)**: выводит первые 10 строк матрицы с их суммой.
4. **getHostName()**: определяет имя узла.
---
## Тестирование
После запуска приложения на двух контейнерах, видим такой вывод (на главном):
```
MPJ Express (0.44) is started in the cluster configuration with niodev
Starting process <0> on <CT117>
Starting process <1> on <CT118>
Node 0 executing on CT117
Node 1 executing on CT118
Sorted matrix (first 10 rows, first 10 elements each) with row sums:
Sum: 470657 -> [908, 526, 945, 193, 501, 547, 564, 355, 259, 717]
Sum: 470813 -> [342, 532, 190, 920, 164, 504, 170, 837, 988, 583]
Sum: 473327 -> [674, 633, 205, 360, 882, 996, 151, 467, 41, 134]
Sum: 474348 -> [665, 904, 777, 331, 574, 94, 938, 893, 659, 352]
Sum: 474754 -> [37, 566, 213, 663, 916, 383, 733, 902, 239, 790]
Sum: 477012 -> [887, 663, 634, 535, 909, 386, 967, 364, 741, 359]
Sum: 477206 -> [784, 974, 714, 510, 556, 682, 502, 597, 689, 972]
Sum: 477311 -> [239, 267, 394, 124, 732, 828, 840, 702, 930, 121]
Sum: 477356 -> [419, 377, 788, 405, 516, 826, 377, 221, 22, 329]
Sum: 477380 -> [765, 7, 696, 942, 902, 983, 310, 775, 443, 888]
Sorting time: 988 ms
Stopping Process <0> on <CT117>
Stopping Process <1> on <CT118>
```
---
## Выводы
- MPI Express обладает рядом существенных преимуществ, как, например, горизонтальное масштабирование, позволяющее подключать большое количество узлов, тем самым максимально увеличивая производительность системы.
- Также к серьезным плюсам MPI можно отнести оптимизацию под НРС, что делает MPI отличным инструментом для кластеризированных задач.
- Если сравнивать технологию MPI с технологиями для многопоточных вычислений, такими как ThreadPoolExecutor и ForkJoinPool, MPI будет предпочтительнее в работе между кластерами по сети (для распределенных систем, где данные находятся на разных узлах), а многопоточные подходы, такие как ThreadPoolExecutor и ForkJoinPool, лучше подходят для задач на одной машине благодаря низким накладным расходам (отсутствуют расходы на передачу сообщений между процессами) и использованию общей памяти.