— метод решения линейной системы (обращения матрицы), с которым вы работали в предыдущем семестре, необходимо адаптировать для исполнения в системах с распределённой памятью с использованием стандарта MPI (о нём — чуть позже).
Требования по ускорению такие же, как и для версии на Pthreads.
Добавляется дополнительное требование на пересылки данных между процессами: не более O(n) пересылок, объём каждой — не более O(n). Про пересылки — тоже чуть позже.
Матрица должна быть распределена между процессами. Если процессов больше одного, ни один процесс не должен выделять память под всю матрицу (или под всю обратную). Использование памяти для P процессов должно быть \(n^2/P + O(n)\) для решения линейной системы и \(2n^2/P + O(n)\) для обращения матрицы.
— реализация двух методов и графического интерфейса к ним (на основе заготовки).
— реализация двух методов (или одного — метода конечных элементов) и графического интерфейса к ним (на основе той же заготовки).
— по тому же принципу, что и в предыдущем семестре.
Современные суперкомпьютеры выглядят приблизительно так:
На фотографии на предыдущем слайде — IBM Blue Gene/P. Он составлен из 72 стоек. В каждой стойке — 16 блейд-серверов, на каждом сервере — несколько процессоров, у каждого из которых — 8 или больше ядер.
Ядра одного процессора имеют быстрый доступ к одному блоку памяти.
Несколько процессоров образуют NUMA-узел — в несколько раз более медленный доступ к участкам памяти, которые принадлежат другим процессорам, но с точки зрения программиста это можно назвать общей памятью (могут работать, например, программы использующие pthreads).
NUMA-узлы связаны интерконнектом. Интерконнект можно считать высокоскоростной сетью — но прямого доступа к памяти соседнего узла интерконнект не предоставляет.
На машинах с распределённой памятью части программы, работающие на разных узлах, передают данные с использованием обмена сообщениями.
На настоящее время стандартом для разработки программ для машин с распределённой памятью является MPI, дословно — «интерфейс передачи сообщений».
pthread_create
создаёт несколько потоков с заданными аргументами и управляет их выполнением.Для работы с MPI во все файлы с исходным кодом, в которых используются функции или типы данных MPI, должен быть включён заголовочный файл mpi.h
:
#include <mpi.h>
Для компиляции используются обёртки над gcc
и g++
— mpicc
и mpicxx
соответственно. Вместо ld
можно использовать mpicc
. Командная строка для компиляции отличается от той, которую вы использовали ранее, только тем, что вместо gcc
используется mpicc
и т.п., все ключи сохраняют своё значение.
gcc -g -lm
mpicc -g -lm myprog.c -o myprog
gcc -O3 myprog.c
mpicc -O3 myprog.c
Запуск осуществляется с использованием команды mpirun
:
mpirun -np количество_процессов имя_программы аргументы_программы
например:
mpirun -np 4 ./a.out --formula 2 -n 2000
Аргументы --formula 2
и -n 2000
передаются в вашу программу (./a.out
), аргумент -np 4
не передаётся.
В начале программы, работающей с MPI, необходимо инициализировать библиотеку MPI с помощью функции MPI_Init
; перед завершением — вызвать функцию завершения MPI_Finalize
.
int MPI_Init(int *argc, char*** argv);
int MPI_Finalize();
Аргументы argc
и argv
функции MPI_Init
должны быть указателями на соответствующие аргументы функции main
, см. пример далее. Функция MPI_Finalize
не содержит аргументов.
Количество запущенных процессов не должно передаваться в программу в качестве отдельного аргумента командной строки, но определяется с использованием функций MPI_Comm_size
. Номер, который автоматически присваивается процессу, может быть получен с помощью функции MPI_Comm_rank
. Пример: при запуске p
процессов, в каждом из результат выполнения MPI_Comm_size
равен p
, а результат выполнения MPI_Comm_rank
— числам от 0
до p-1
. Нулевой процесс обычно для простоты считают «главным» и выполняют из него весь ввод-вывод (при разработке допускается использовать отладочный printf
-вывод во всех процессах).
int MPI_Comm_rank(MPI_Comm comm, int *rank);
int MPI_Comm_size(MPI_Comm comm, int *total_procs);
Далее под «номером процесса» будем понимать число, которое возвращает в переменной rank
вызов функции MPI_Comm_rank
.
#include <mpi.h>
#include <stdio.h>
int main(int argc, char **argv) {
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
printf("Process #%d of %d\n", rank, size);
MPI_Finalize();
return 0;
}