Files
SSPR_25/kuznetsov_danila_lab_2
..
2025-03-12 17:19:59 +04:00
2025-03-12 17:19:59 +04:00

Лабораторная работа 2

1. Задание

Разработка параллельного MPI приложения на языке Java. Необходимо разработать параллельный вариант алгоритма с применением MPI и замерить время его работы. Запуск каждого экземпляра MPI происходит в своём контейнере.

Вариант: "Определить минимальный элемент матрицы ниже главной диагонали"

2. Описание кода

package ru.kuznec;

import mpi.MPI;

public class MatrixMinBelowMainDiagonal {
    public static void main(String[] args) {
        MPI.Init(args);
        
        int rank = MPI.COMM_WORLD.Rank();
        int size = MPI.COMM_WORLD.Size();

        int n = 4;
        double[] matrix = new double[n * n];

        if (rank == 0) {
            double[][] m = {
                    {1, 2, 3, 4},
                    {5, 6, 7, 8},
                    {9, 10, 11, 12},
                    {13, 14, 15, 16}
            };
            
            for (int i = 0; i < n; i++) {
                System.arraycopy(m[i], 0, matrix, i * n, n);
            }
        }
        
        int[] dim = new int[]{n};
        MPI.COMM_WORLD.Bcast(dim, 0, 1, MPI.INT, 0);
        n = dim[0];

        MPI.COMM_WORLD.Bcast(matrix, 0, n * n, MPI.DOUBLE, 0);

        int rowsPerProc = n / size;
        int extra = n % size;
        int startRow, endRow;
        if (rank < extra) {
            startRow = rank * (rowsPerProc + 1);
            endRow = startRow + rowsPerProc;
        } else {
            startRow = rank * rowsPerProc + extra;
            endRow = startRow + rowsPerProc - 1;
        }

        double localMin = Double.MAX_VALUE;
        for (int i = startRow; i <= endRow; i++) {
            for (int j = 0; j < i; j++) {
                double value = matrix[i * n + j];
                if (value < localMin) {
                    localMin = value;
                }
            }
        }

        double[] sendBuf = new double[]{ localMin };
        double[] globalMin = new double[1];
        MPI.COMM_WORLD.Reduce(sendBuf, 0, globalMin, 0, 1, MPI.DOUBLE, MPI.MIN, 0);

        if (rank == 0) {
            if (globalMin[0] == Double.MAX_VALUE) {
                System.out.println("Элементы ниже главной диагонали отсутствуют.");
            } else {
                System.out.println("Минимальный элемент ниже главной диагонали: " + globalMin[0]);
            }
        }

        MPI.Finalize();
    }
}
  1. Метод MPI.Init(args) запускает параллельную среду. Каждый процесс получает свой уникальный ранг и общее число процессов через MPI.COMM_WORLD.Rank() и MPI.COMM_WORLD.Size().

  2. Только процесс с рангом 0 (мастер-процесс) задаёт исходную матрицу. Для удобства обмена данными матрица преобразуется в одномерный массив с помощью System.arraycopy.

  3. Сначала размер матрицы (n) передаётся всем процессам, затем вся матрица передаётся всем процессам с помощью коллективной операции Bcast.

  4. Матрица разбивается на блоки строк для каждого процесса. Если число процессов не делит размер матрицы нацело, первые процессы получают на одну строку больше (extra).

  5. Каждый процесс просматривает свои строки и ищет минимальный элемент среди элементов ниже главной диагонали (для строки с индексом i обрабатываются столбцы от 0 до i-1).

  6. Операция Reduce с оператором MPI.MIN объединяет локальные минимумы в глобальный, который выводится на экран процессом с рангом 0.

  7. После выполнения всех вычислений вызывается MPI.Finalize(), что корректно завершает параллельную среду.

4. Действия, производимые на контейнерах

Для запуска этого MPI-приложения в распределённом режиме использовались контейнеры, каждый из которых настроен следующим образом:

  • Установка Java и MPJ Express:
    На каждом контейнере установлена jdk-21 и распакована библиотека MPJ Express. Переменная MPJ_HOME настроена на путь к MPJ Express, а каталог MPJ_HOME/bin добавлен в переменную PATH.

  • Настройка сети и SSH:
    Контейнеры настроены так, чтобы иметь возможность обмениваться данными по сети. Для работы MPJ Express настроен passwordless SSH между контейнерами. Файл machines содержит IP-адреса всех контейнеров:

    192.168.10.101
    192.168.10.102
    192.168.10.103
    192.168.10.104
    

    Это позволяет MPJ Express распределить процессы по разным контейнерам.

  • Запуск демонов MPJ Express:
    На каждом контейнере запущен MPJ daemon, который слушает входящие соединения от мастера. Демоны обеспечивают обмен информацией о портах и корректное распределение процессов.

    $MPJ_HOME/bin/mpjdaemon -boot 192.168.10.101
    $MPJ_HOME/bin/mpjdaemon -boot 192.168.10.102
    $MPJ_HOME/bin/mpjdaemon -boot 192.168.10.103
    $MPJ_HOME/bin/mpjdaemon -boot 192.168.10.104
    
  • Добавление хостов в каждый контейнер На каждом контейнере добавляем в файл /etc/hosts каждый хост, на одном из контейнеров это выглядит примерно так:

      # --- BEGIN PVE ---
      192.168.10.101 node1.com node1
      192.168.10.102 node2.com node2
      192.168.10.103 node3.com node3
      192.168.10.104 node4.com node4
      # --- END PVE ---
    
  • Запуск MPI-приложения:
    Из одного из контейнеров запускается приложение с помощью команды:

    mpjrun.sh -np 4 -dev niodev MatrixMinBelowMainDiagonal
    

    Эта команда запускает 4 процесса, которые распределяются между контейнерами согласно файлу machines.