SlideShare a Scribd company logo
Лекция 1.
Базовые понятия стандарта MPI.
Дифференцированные обмены
Пазников Алексей Александрович
Параллельные вычислительные технологии
СибГУТИ (Новосибирск)
Осенний семестр 2015
www: cpct.sibsutis.ru/~apaznikov/teaching
q/a: piazza.com/sibsutis.ru/fall2015/pct2015fall
Особенности
распределённых
вычислительных
систем
Вычислительные системы с распределённой памятью
3
CPU 1
Кэш
CPU 3
Кэш
CPU n
Кэш
CPU 1
Кэш
CPU 2
Кэш
CPU 3
Кэш
CPU n
Кэш
Общая память (shared memory) Общая память (shared memory)
Коммуникационная сеть (Interconnect, Communication network)
Распределённая вычислительная система (ВС) (distributed computer system)
представляет собой совокупность вычислительных узлов, взаимодействующих через
коммуникационную сеть (Gigabit Ethernet, InfiniBand, Cray Gemeni, Fujitsu Tofu, etc)
▪ Каждый узел включает в себя несколько процессоров (CPU 1, …, CPU n),
взаимодействующих через общую память (shared memory).
CPU 2
Кэш
CPU 2CPU 1
Вычислительные системы с распределённой памятью
4
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Общая память (shared memory)
Коммуникационная сеть (Interconnect, Communication network)
Распределённая вычислительная система (ВС) (distributed computer system)
представляет собой совокупность вычислительных узлов, взаимодействующих через
коммуникационную сеть (Gigabit Ethernet, InfiniBand, Cray Gemeni, Fujitsu Tofu, etc)
▪ Каждый узел включает в себя несколько процессоров (CPU1...CPUn),
взаимодействующих через общую память (shared memory).
▪ Процессоры могут быть многоядерными (multicore) и ядра могут поддерживать
одновременное выполнение нескольких потоков (SMT, Hyper-threading).
Кэш L2 Кэш L2
CPU 2CPU 1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Общая память (shared memory)
Кэш L2 Кэш L2
CPU 2CPU 1
Вычислительные системы с распределённой памятью
5
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Общая память (shared memory)
Коммуникационная сеть (Interconnect, Communication network)
▪ Instruction Level Parallelism (ILP) – параллелизм уровня инструкций. Суперскалярный
конвейер, внеочередное выполнение команд. SSE/AVX, AltivEC, ARM NEON
▪ Thread Level Parallelism (TLP) – параллелизм потоков. POSIX Threads, OpenMP, C++-
threads, Cilk, Intel TBB, GPGPU (OpenCL, CUDA, OpenACC)
▪ Process Level Parallelism (PLP) (message passing) – параллелизм уровня процессов,
интерфейс обмена сообщениями. MPI, OpenSHMEM, PGAS (Cray Chapel, IBM X10, UPC)
Кэш L2 Кэш L2
CPU 2CPU 1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Ядро 1
Кэш L1
Ядро 2
Кэш L1
Общая память (shared memory)
Кэш L2 Кэш L2
ILP ILP ILPILP ILP ILPILPILP
TLP / PLP TLP / PLP TLP / PLP TLP / PLP
TLP / PLP TLP / PLP
PLP
Вычислительный узел 1
CPU 1
Вычислительные кластеры
6
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Головной узел
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Сервисная сеть (NFS, DNS, DHCP, ssh)
Вычислительная сеть (MPI)
Вычислительные кластеры (computer clusters) строятся
на базе свободно доступных компонентов.
▪ Вычислительные узлы
▪ Головной узел (frontend)
▪ Вычислительная сеть
▪ Сервисная сеть
▪ Подсистема хранения данных
▪ Подсистема
бесперебойного
электропитания
▪ Система охлаждения
▪ Программное
обеспечение
Рейтинги суперкомпьютеров: TOP500
7
№ Место Система Число
ядер
Макс.
произв.,
TFLOPS
Пиковая
произв.,
TFLOPS
Эл. эн.,
KW
1 National Super Computer
Center in Guangzhou
China
Tianhe-2 (MilkyWay-2) - TH-IVB-
FEP Cluster, Intel Xeon E5-2692 12C
2.200GHz, TH Express-2, Intel Xeon
Phi 31S1P, NUDT
3,120,000 33,862.7 54,902.4 17,808
2 DOE/SC/Oak Ridge
National Laboratory
United States
Titan - Cray XK7 , Opteron 6274 16C
2.200GHz, Cray Gemini interconnect,
NVIDIA K20x
Cray Inc.
560,640 17,590.0 27,112.5 8,209
3 DOE/NNSA/LLNL
United States
Sequoia - BlueGene/Q, Power BQC
16C 1.60 GHz, Custom
IBM
1,572,864 17,173.2 20,132.7 7,890
4 RIKEN
Japan
K computer, SPARC64 VIIIfx 2.0
GHz, Tofu interconnect
Fujitsu
705,024 10,510.0 11,280.4 12,660
5 DOE/SC/Argonne
National Laboratory
United States
Mira - BlueGene/Q, Power BQC 16C
1.60GHz, Custom
IBM
786,432 8,586.6 10,066.3 3,945
Рейтинги суперкомпьютеров: TOP500
8
Архитектурные особенности современных суперкомпьютеров:
▪ Большемасштабность: более 1000000 процессорных ядер
▪ Многоядерные процессоры с количеством ядер более 10 на процессор
▪ Высокое энергопотребление: несколько MW
▪ Коммуникационная сеть: Infiniband, Gigabit Ethernet, 10 Gigabit Ethernet, Cray
Gemini, TH Express, Tofu Interconnect
▪ Процессоры: Intel, IBM Power, AMD Opteron, …
▪ Гетерогенные системы: наличие ускорителей NVIDIA GPU, Intel Xeon Phi,
Tilera Tile-GX, PLD-accelerators,
▪ Операционные системы GNU/Linux (преимущественно), IBM AIX и др.
Введение
в MPI
Стандарт MPI
10
Message Passing Interface (MPI, интерфейс передачи сообщений) – стандарт
программного интерфейса коммуникационных функций для создания переносимых
параллельных программ в модели передачи сообщений (message passing).
▪ Стандарт реализован в MPI-библиотеках MPICH, Open MPI, Intel MPI и т.д.
▪ MPI появился в 1991 и был предложен группой учёных. С тех пор появился
семинар по стандарту Workshop on Standards for Message passing in a Distributed
Memory environment и сформирован комитет по стандартизации. Создан MPI-
форум.
▪ MPI определяет синтаксис и семантику функций библиотек передачи
сообщений для языков Fortran и C.
▪ Является стандартным средством для организации взаимодействия процессов
в параллельных программах, выполняющихся на распределённых ВС.
▪ Обеспечивается переносимость программ между разными архитектурами
процессоров (Cray, Intel, IBM, NEC, …)
▪ Последний стандарт MPI-3.1. вышел 4 июня 2015 г.
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
11
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0
▪ MPI-программы включают N параллельно работающих процессов
▪ Каждый процесс назначается на отдельное процессорное ядро
▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N
P1 P2 P3 P4 P5
▪ Одна MPI-программа из 6 процессов (программа ранга 6)
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
12
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0
▪ MPI-программы включают N параллельно работающих процессов
▪ Каждый процесс назначается на отдельное процессорное ядро
▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N
P1 P2 P3 P4 P5
▪ Четыре программы рангов 6, 1, 3, 2
▪ Одна программа может быть запущена с разным количеством процессов
P0 P0 P1 P2 P0 P1
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
13
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
▪ MPI-программы включают N параллельно работающих процессов
▪ Каждый процесс назначается на отдельное процессорное ядро
▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N
P0 P1 P2 P3 P4 P5
if (myrank == 0) {
goright();
} else if (myrank == 1) {
goleft();
}
Каждый процесс выполняет свою отдельную подпрограмму
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
14
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
▪ MPI-программы включают N параллельно работающих процессов
▪ Каждый процесс назначается на отдельное процессорное ядро
▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N
Каждый процесс выполняет свою отдельную подпрограмму
P0 P1 P2 P3 P4 P5
if (myrank == 0) {
goright();
} else if (myrank == 1) {
goleft();
} else {
stand_and_wait();
}
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
15
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
▪ Процессы взаимодействуют между собой путём обмена сообщениями
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
16
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
▪ Процессы взаимодействуют между собой путём обмена сообщениями
▪ Или путём выполнений функций одностороннего доступа к памяти
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
17
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
▪ Коммуникатор в MPI это группа процессов.
▪ Все процессы MPI-программы формируют коммуникатор MPI_COMM_WORLD,
который предопределён в файле mpi.h.
▪ Каждый процесс имеет свой ранг в пределах коммуникатора (начинающийся с 0 и
заканчивающийся на commsize – 1)
MPI_COMM_WORLD
Вычислительный узел 1
CPU 1
Основные понятия MPI-программ
18
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
▪ Получить ранг процесса можно с помощью функции MPI_Comm_rank
MPI_COMM_WORLD
int MPI_Comm_rank(MPI_Comm comm, int *rank);
int MPI_Comm_size(MPI_Comm comm, int *size);
▪ Количество процессов в коммуникаторе
19
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
int size, rank, name_len;
// Проинициализировать библиотеку MPI
MPI_Init(NULL, NULL);
// Получить число процессов
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Получить ранг процесса
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Получить имя процесса
char procname[MPI_MAX_PROCESSOR_NAME];
MPI_Get_processor_name(processor_name, &name_len);
// Напечатать сообщения каждым процессом
printf("Hello world from processor %s, rank %d"
" out of %d processorsn",
processor_name, world_rank, world_size);
// Завершить среду MPI
MPI_Finalize();
}
Заголовочный файл mpi.h
MPI-функции
имеют префикс
MPI_
Инициализация
среды MPI
Завершение
среды MPI
Структура MPI-программы. “hello world”
Структура MPI-программы. “hello world”
20
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
int size, rank, name_len, rc;
// Проинициализировать библиотеку MPI
rc = MPI_Init(NULL, NULL);
if (rc == MPI_SUCCESS) {
// Получить число процессов
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Получить ранг процесса
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Получить имя процесса
char procname[MPI_MAX_PROCESSOR_NAME];
int name_len;
MPI_Get_processor_name(processor_name, &name_len);
// Напечатать сообщения каждым процессом
printf(...);
// Завершить среду MPI
MPI_Finalize();
} else {
fprintf(stderr, "Can't initialize MPI!n");
}
}
Заголовочный файл mpi.h
MPI-функции
имеют префикс
MPI_
MPI-функции
возвращают
MPI_SUCCESS
в случае успеха
и код ошибки в
противном
случае
Структура MPI-программы. “hello world”
21
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
int size, rank, name_len, rc;
// Проинициализировать библиотеку MPI
rc = MPI_Init(NULL, NULL);
if (rc == MPI_SUCCESS) {
// Получить число процессов
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Получить ранг процесса
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Получить имя процесса
char procname[MPI_MAX_PROCESSOR_NAME];
int name_len;
MPI_Get_processor_name(processor_name, &name_len);
// Напечатать сообщения каждым процессом
printf(...);
if (rank == 0)
printf("hello world!n");
// Завершить среду MPI
MPI_Finalize();
}
Заголовочный файл mpi.h
Только нулевой
процесс печатает
сообщение “hello
world”
Компиляция MPI-программ
22
$ mpicc -Wall -O2 -o prog prog.c
Программа на С
Программа на С++
$ mpicxx -Wall -O2 -o prog prog.c
Программа на Fortran
$ mpif90 -Wall -O2 -o prog prog.c
Запуск MPI-программ на кластере (система TORQUE)
23
#PBS -N mpi_hello
#PBS -l nodes=2:ppn=8
#PBS -j oe
cd $PBS_O_WORKDIR
mpiexec ./hello
Паспорт задачи (task.job) для запуска на двух узлах с использованием восьми ядер с
каждого узла
$ qsub task.job
1849
$ qstat
Job ID Name User Time Use S Queue
----------------- ------------ ------- --------- - -----
1849 mpi_hello ap 00:00:00 R debug
Запуск задачи на кластере
Запуск MPI-программ на кластере (система TORQUE)
24
$ qstat
Job ID Name User Time Use S Queue
----------------- ------------ ------- --------- - -----
1849 mpi_hello ap 00:00:00 C debug
$ cat mpi_hello.o1849
Hello, MPI World! Process 8 of 16 on node cn1.
Hello, MPI World! Process 9 of 16 on node cn1.
Hello, MPI World! Process 10 of 16 on node cn1.
Hello, MPI World! Process 11 of 16 on node cn1.
Hello, MPI World! Process 12 of 16 on node cn1.
Hello, MPI World! Process 0 of 16 on node cn2.
Hello, MPI World! Process 3 of 16 on node cn2.
Hello, MPI World! Process 4 of 16 on node cn2.
Hello, MPI World! Process 5 of 16 on node cn2.
Hello, MPI World! Process 6 of 16 on node cn2.
Hello, MPI World! Process 7 of 16 on node cn2.
Hello, MPI World! Process 13 of 16 on node cn1.
Hello, MPI World! Process 14 of 16 on node cn1.
Hello, MPI World! Process 15 of 16 on node cn1.
Hello, MPI World! Process 1 of 16 on node cn2.
Hello, MPI World! Process 2 of 16 on node cn2.
Запуск задачи на кластере
Вычислительный узел 1
CPU 1
25
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 3
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Головной узел
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Сервисная сеть (NFS, DNS, DHCP, ssh)
Вычислительная сеть (MPI)
Выполнение MPI-программы на кластере
qsub task.job
Запуск MPI-программы на кластере
(что происходит при выполнении
qsub task.job)
Вычислительный узел 1
CPU 1
26
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 3
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Головной узел
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Сервисная сеть (NFS, DNS, DHCP, ssh)
Вычислительная сеть (MPI)
Выполнение MPI-программы на кластере
qsub task.job
Запуск MPI-программы на кластере
(что происходит при выполнении
qsub task.job)
1. TORQUE выделят подсистему
процессорных ядер по паспорту
задачи.
Вычислительный узел 1
CPU 1
27
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 2
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 3
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Головной узел
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Сервисная сеть (NFS, DNS, DHCP, ssh)
Вычислительная сеть (MPI)
Выполнение MPI-программы на кластере
mpiexec ./prog
P0 P1 P2 P3 P4 P5
prog
prog
prog
prog
prog
prog
ssh cn2 ssh cn3
Запуск MPI-программы на кластере
(что происходит при выполнении
qsub task.job)
1. TORQUE выделят подсистему
процессорных ядер по паспорту
задачи.
2. mpiexec (скрипт) заходит по ssh
на узлы, на которых выделена
подсистема и запускает
одинаковые экземпляры MPI-
программы.
Подсчет количества простых чисел (последовательная версия)
28
int is_prime_number(int n) {
int limit = sqrt(n) + 1;
for (int i = 2; i <= limit; i++) {
if (n % i == 0)
return 0;
}
return (n > 1) ? 1 : 0;
}
int count_prime_numbers(int a, int b) {
int nprimes = 0;
if (a <= 2) {
nprimes = 1; // Посчитать '2' как простое
a = 2;
}
if (a % 2 == 0)
a++; // Сместить 'a' до нечётного числа
// Цикл по нечётным числам: a, a + 2, a + 4, ... , b
for (int i = a; i <= b; i += 2)
if (is_prime_number(i))
nprimes++;
return nprimes;
}
Определяет, является ли
число n простым
Подсчитывает
количество
простых чисел в
интервале [a, b]
Подсчет количества простых чисел (MPI версия)
29
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);
// Запустить последовательную версию
double tserial = 0;
if (get_comm_rank() == 0)
tserial = run_serial();
// Дождаться завершения выполнения последовательной версиии
// нулевым процессом
MPI_Barrier(MPI_COMM_WORLD);
// Запустить параллельную версию
double tparallel = run_parallel();
if (get_comm_rank() == 0) {
printf("Count prime numbers on [%d, %d]n", a, b);
printf("Execution time (serial): %.6fn", tserial);
printf("Execution time (parallel): %.6fn", tparallel);
printf("Speedup (processes %d): %.2fn", get_comm_size(),
tserial / tparallel);
}
MPI_Finalize();
return 0;
}
Подсчет количества простых чисел (MPI версия)
30
double run_serial() {
double t = MPI_Wtime();
int n = count_prime_numbers(a, b);
t = MPI_Wtime() - t;
printf("Result (serial): %dn", n);
return t;
}
double run_parallel() {
double t = MPI_Wtime();
int n = count_prime_numbers_par(a, b);
t = MPI_Wtime() - t;
printf("Process %d/%d execution time: %.6fn",
get_comm_rank(), get_comm_size(), t);
if (get_comm_rank() == 0)
printf("Result (parallel): %dn", n);
return t;
}
MPI-функция измерения
временных интервалов
Получение ранга процесса
и количества процессов
Подсчет количества простых чисел (MPI версия)
31
int get_comm_rank() {
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
return rank;
}
int get_comm_size() {
int size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
return size;
}
Подсчет количества простых чисел (MPI версия)
32
void get_chunk(int a, int b, int commsize, int rank,
int *lb, int *ub) {
int n = b - a + 1;
int q = n / commsize;
if (n % commsize)
q++;
int r = commsize * q - n;
// Расчитать размер порции данных для процесса
int chunk = q;
if (rank >= commsize - r)
chunk = q - 1;
// Определить начало порции для процесса
*lb = a;
if (rank > 0) {
// Подсчитать сумму предыдущих порций
if (rank <= commsize - r)
*lb += q * rank;
else
*lb += q * (commsize - r) + (q - 1) * (rank - (commsize - r));
}
*ub = *lb + chunk - 1;
}
Подсчет количества простых чисел (MPI версия)
33
int count_prime_numbers_par(int a, int b) {
int nprimes = 0;
int lb, ub;
get_chunk(a, b, get_comm_size(), get_comm_rank(), &lb, &ub);
if (lb <= 2) {
nprimes = 1; // Посчитать '2' как простое
lb = 2;
}
if (lb % 2 == 0)
lb++; // Сместить 'lb' до нечётного числа
// Цикл по нечётным числам: a, a + 2, a + 4, ... , b
for (int i = lb; i <= ub; i += 2) {
if (is_prime_number(i))
nprimes++;
int nprimes_global;
MPI_Reduce(&nprimes, &nprimes_global, 1,
MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
return nprimes_global;
}
Подсчитать сумму простых
чисен, найденных всеми
процессами
Определение ускорения параллельной программы
34
Process 1/8 execution time: 0.641697
Process 3/8 execution time: 0.915595
Process 2/8 execution time: 0.915649
Process 5/8 execution time: 1.104982
Process 7/8 execution time: 1.267695
Process 6/8 execution time: 1.267713
Process 4/8 execution time: 1.267691
Result (serial): 664579
Process 0/8 execution time: 0.390985
Result (parallel): 664579
Count prime numbers on [1, 10000000]
Execution time (serial): 7.310900
Execution time (parallel): 0.390985
Speedup (processes 8): 18.70
Проблема 1
Дисбаланс загрузки
процессов
Проблема 2
Неверно подсчитанное
ускорение
Подсчет количества простых чисел (MPI версия)
35
int count_prime_numbers_par(int a, int b) {
int nprimes = 0;
int lb, ub;
get_chunk(a, b, get_comm_size(), get_comm_rank(), &lb, &ub);
if (lb <= 2) {
nprimes = 1; // Посчитать '2' как простое
lb = 2;
}
if (lb % 2 == 0)
lb++; // Сместить 'lb' до нечётного числа
// Цикл по нечётным числам: a, a + 2, a + 4, ... , b
for (int i = lb; i <= ub; i += 2) {
if (is_prime_number(i))
nprimes++;
int nprimes_global;
MPI_Reduce(&nprimes, &nprimes_global, 1,
MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
return nprimes_global;
}
Проблема 1
Неравномерно загрузка процессов
Процесс 0: 0, 1, 2, 3
Процесс 1: 4, 5, 6, 7
Процесс 3: 8, 9, 10, 11
Подсчет количества простых чисел (MPI версия)
36
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);
// Запустить последовательную версию
double tserial = 0;
if (get_comm_rank() == 0)
tserial = run_serial();
// Запустить параллельную версию
double tparallel = run_parallel();
if (get_comm_rank() == 0) {
printf("Count prime numbers on [%d, %d]n", a, b);
printf("Execution time (serial): %.6fn", tserial);
printf("Execution time (parallel): %.6fn", tparallel);
printf("Speedup (processes %d): %.2fn", get_comm_size(),
tserial / tparallel);
}
MPI_Finalize();
return 0;
}
Проблема 2
Неверно подсчитанное ускорение
s = t1
/ tp
tp
– время процесса 0
За tp
необходимо принять наибольшее
время выполнения процессов
Подсчет количества простых чисел (MPI версия)
37
double run_parallel() {
double t = MPI_Wtime();
int n = count_prime_numbers_par(a, b);
t = MPI_Wtime() - t;
printf("Process %d/%d execution time: %.6fn",
get_comm_rank(), get_comm_size(), t);
if (get_comm_rank() == 0)
printf("Result (parallel): %dn", n);
// Собираем в процессе 0 максимальное из времён выполнения
double tmax;
MPI_Reduce(&t, &tmax, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
return tmax;
}
Решение проблемы 2
s = t1
/ (max(t0
, t1
, …, tn
))
Определить максимальное
среди всех времён
выполнения процессов.
Подсчет количества простых чисел (MPI версия)
38
int count_prime_numbers_par(int a, int b) {
int nprimes = 0;
// Посчитать '2' как простое
int commsize = get_comm_size();
int rank = get_comm_rank();
if (a <= 2) {
a = 2;
if (rank == 0)
nprimes = 1;
}
for (int i = a + rank; i <= b; i += commsize) {
if (i % 2 > 0 && is_prime_number(i))
nprimes++;
}
int nprimes_global = 0;
MPI_Reduce(&nprimes, &nprimes_global, 1,
MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
return nprimes_global;
}
Решение проблемы 1
Циклическое распределение
итераций (round-robin)
Вычислительный узел 1
CPU 1
39
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3
Подсчет количества простых чисел (MPI версия)
1 2 43 5 7 87 9 11 1211 13 15 1615 17 18 20192 6 10 14
commsize
Решение проблемы 1
Циклическое распределение
итераций (round-robin)
Дифференцированные
обмены
Дифференцированные обмены
41
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
Сообщения представляют собой пакеты данных, которые “перемещаются” между
процессами.
Сообщение имеет следующие параметры:
▪ процесс-отправитель
▪ адрес отправляемых данных
▪ тип отправляемых данных
▪ размер данных
▪ процесс-получатель
▪ адрес данных для получения
▪ тип данных назначения
▪ размер буфера для получения
Дифференцированные обмены
42
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел 1
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительный узел N
CPU 1
Ядро Ядро
Общая память (shared memory)
CPU 2
Ядро Ядро
Вычислительная сеть (MPI)
P0 P1 P2 P3 P4 P5
Дифференцированный обмен – простейший вид обменов, обмен вида “точка-точка”.
Один процесс передаёт сообщение другому.
Виды дифференцированных обменов:
▪ синхронная отправка
▪ асинхронная (буферезированная) отправка
▪ отправка сообщения “по готовности”
▪ неблокируемые обмены
43
Отправка и получение дифференцированных сообщений
int MPI_Send(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
▪ buf – адрес для начала отправляемого сообщения с количеством count
элементов типа datatype
▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе
с сообщением; тэг может быть использован программой для разделения разных
типов сообщений.
▪ comm – коммуникатор, в котором находятся процессы.
int MPI_Recv(void *buf, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm, MPI_Status *status)
▪ buf, count, datatype – описывают буфер для получения.
▪ source – ранг процесса в коммуникаторе comm, который отправляет
сообщение.
▪ status – результат выполнения операции.
▪ Только сообщение с тэгом tag будет получено.
44
Отправка и получение дифференцированных сообщений
int MPI_Send(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
▪ buf – адрес для начала отправляемого сообщения с количеством count
элементов типа datatype
▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе
с сообщением; тэг может быть использован программой для разделения разных
типов сообщений.
▪ comm – коммуникатор, в котором находятся процессы.
int MPI_Recv(void *buf, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm, MPI_Status *status)
▪ buf, count, datatype – описывают буфер для получения.
▪ source – ранг процесса в коммуникаторе comm, который отправляет сообщение
(возможно значение source = MPI_ANY_SOURCE для получения сообщения от
любого источника).
▪ status – результат выполнения операции
(возможно значение status = MPI_STATUS_IGNORE).
▪ Только сообщение с тэгом tag будет получено.
(возможно значение tag = MPI_ANY_TAG для получения сообщения
с любым тегом).
45
Требования для выполнения дифференцированного обмена
▪ Отправитель должен указать необходимый ранг получателя.
▪ Получатель должен указать корректный ранг отправителя (или получатель
использует MPI_ANY_SOURCE).
▪ Коммуникатор должен быть одним и тем же.
▪ Тэги должны совпадать (или получатель использует MPI_ANY_TAG)
▪ Типы данных должны при отправке и получении должны совпадать.
▪ Буфер получателя должен быть достаточно большим.
46
Требования для выполнения дифференцированного обмена
▪ Отправитель должен указать необходимый ранг получателя.
▪ Получатель должен указать корректный ранг отправителя (или получатель
использует MPI_ANY_SOURCE).
▪ Коммуникатор должен быть одним и тем же.
▪ Тэги должны совпадать (или получатель использует MPI_ANY_TAG)
▪ Типы данных должны при отправке и получении должны совпадать.
▪ Буфер получателя должен быть достаточно большим.
Параметр status получателя содержит актуальную информацию о полученном
сообщении:
▪ отправитель: status.MPI_SOURCE
▪ тэг: status.MPI_TAG
▪ количество полученных сообщений:
int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype,
int *count)
47
Дифференцированные обмены: “hello world”
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);
int number;
if (get_comm_rank() == 0) {
number = 42;
MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (get_comm_rank() == 1) {
MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0n", number);
}
MPI_Finalize();
return 0;
}
Process 1 received number 42 from process 0
48
Дифференцированные обмены: “hello world”
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int number;
if (get_comm_rank() == 0) {
number = 42;
MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (get_comm_rank() == 1) {
MPI_Status status;
MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
int count;
MPI_Get_count(&status, MPI_INT, &count);
printf("Process 1 received number %d from process 0n"
"with status source %d, tag %d, count %dn",
number, status.MPI_SOURCE, status.MPI_TAG, count);
}
MPI_Finalize();
}
Process 1 received number 42 from process 0
with status source 0, tag 0, count 1
49
Классификация дифференцированных обменов
Дифференцированные обмены
Блокируемые Неблокируемые
По наличию
дополнительного буфера
По способу возврата
из функции
Синхронные
Асинхронные
(буферезированные)
50
▪ Выполнение процесса-отправителя приостанавливается, пока сообщение
не будет доставлено.
▪ Передача сообщения начинается только тогда, когда принимающий процесс не
вызовет функцию получения.
▪ При передаче не используются дополнительные буферы.
▪ Отправитель получает информацию о том, что сообщение было получено.
Синхронная отправка сообщений
51
Синхронная отправка сообщений
int MPI_Ssend(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
int MPI_Send(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
Принудительная синхронная отправка сообщения
Тип отправки может быть синхронный (определяется библиотекой)
52
▪ При отправке сообщения используется дополнительный буфер (на стороне
отправителя или на стороне получателя).
▪ Выполнение процесса-отправителя может быть продолжено до того, как
получатель вызовет функцию получения (т.е. вызов функции получения не
обязателен!).
▪ Отправитель “знает” о том, что сообщение было отправлено, но доставка не
гарантируется.
Асинхронная (буферезированная) отправка
53
Асинхронная (буферезированная) отправка
int MPI_Bsend(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
int MPI_Send(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
Тип отправки может быть буферезированный (определяется библиотекой)
Принудительная буферезированная отправка сообщения
54
К блокируемым операциям относятся операции
▪ отправки сообщения
▪ получения сообщения
Операции блокируются до тех пор, пока другой процесс не выполнит
определённую операцию
▪ синхронная отправка блокируется до тех пор, пока сообщение не будет
получено
▪ операция получения блокируется до тех пор, пока сообщение не будет
отправлено
Возврат из функций блокируемых операций происходит только тогда, когда
операция завершается.
Блокируемые операции
MPI_Send
MPI_Ssend
MPI_Bsend
MPI_Recv
55
▪ Неблокируемые операции возвращают выполнение немедленно и позволяют
продолжить выполнение функции, из которой они были вызваны.
▪ Спустя некоторое время можно проверить (test) состояние отправки сообщения
или ждать (wait) до завершения неблокируемой операции.
Неблокируемые операции
MPI_Isend
MPI_Irecv
MPI_Test
MPI_Wait
56
Сообщения содержат некоторое число элементов некоторого типа данных.
В MPI типы бывают
▪ Базовые (basic)
Типы сообщений
MPI Datatype C datatype
MPI_CHAR signed char
MPI_SHORT signed short int
MPI_INT signed int
MPI_LONG signed long int
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short int
MPI_UNSIGNED unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLE long double
57
Сообщения содержат некоторое число элементов некоторого типа данных.
В MPI типы бывают
▪ Базовые (basic)
▪ Производные (derived), которые могут быт получены из базовых
и производных типов.
Примеры сообщений в MPI
Соответствия между базовыми типами MPI и типами С:
Типы сообщений
58
Сохранение порядка отправки сообщений
Правило для сообщений, имеющих одинаковый коммуникатор, источник,
приёмник и тэг:
▪ Сообщения не “обгоняют” друг друга: они доставляются в том же порядке, в
котором они были отправлены.
▪ Правило верно также для несинхронных сообщений.
P1 P2
59
Пример: пинг-понг
▪ Процесс 0 передаёт сообщение процессу 1 (ping). В качестве сообщения
используется счетчик переданных сообщений.
▪ Процесс 0 инкрементирует счетчик сообщений.
▪ После получения, процесс 1 передаёт сообщение обратно процессу 0 (pong).
▪ Процедура повторяется PING_PONG_LIMIT раз.
P1 P2
0
1
ping
pong
pingpong_count
2
3
4
5
6
7
60
Пример: пинг-понг – вариант 1
// Программа работает только для двух процессах
if (get_comm_size() != 2) {
fprintf(stderr, "World size must be two for %sn", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int pingpong_count = 0;
while (pingpong_count < PING_PONG_LIMIT)
if (get_comm_rank() == 0) {
pingpong_count++; // Увеличиваем счетчик перед отправкой
MPI_Send(&pingpong_count, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent and incremented pingpong_count %dn",
pingpong_count);
MPI_Recv(&pingpong_count, 1, MPI_INT, 1, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("0 received pingpong_count %d from 1n", pingpong_count);
} else {
MPI_Recv(&pingpong_count, 1, MPI_INT, 0, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("1 received pingpong_count %d from 0n", pingpong_count);
pingpong_count++; // Увеличиваем счетчик перед отправкой
MPI_Send(&pingpong_count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
printf("1 sent and incremented pingpong_count %d to 1n",
pingpong_count);
}
61
// Программа работает только для двух процессах
if (get_comm_size() != 2) {
fprintf(stderr, "World size must be two for %sn", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int pingpong_count = 0;
int partner_rank = (get_comm_rank() + 1) % 2;
while (pingpong_count < PING_PONG_LIMIT) {
if (get_comm_rank() == pingpong_count % 2) {
// Увеличиваем счетчик перед отправкой
pingpong_count++;
MPI_Send(&pingpong_count, 1, MPI_INT, partner_rank, 0,
MPI_COMM_WORLD);
printf("%d sent and incremented ping_pong_count %d to %dn",
get_comm_rank(), pingpong_count, partner_rank);
} else {
MPI_Recv(&pingpong_count, 1, MPI_INT, partner_rank, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("%d received pingpong_count %d from %dn",
get_comm_rank(), pingpong_count, partner_rank);
}
}
}
Пример: пинг-понг – вариант 2
62
Пример: пинг-понг
0 sent and incremented pingpong_count 1 to 1
1 received pingpong_count 1 from 0
1 sent and incremented pingpong_count 2 to 0
0 received pingpong_count 2 from 1
0 sent and incremented pingpong_count 3 to 1
0 received pingpong_count 4 from 1
0 sent and incremented pingpong_count 5 to 1
0 received pingpong_count 6 from 1
0 sent and incremented pingpong_count 7 to 1
0 received pingpong_count 8 from 1
1 received pingpong_count 3 from 0
1 sent and incremented pingpong_count 4 to 0
1 received pingpong_count 5 from 0
1 sent and incremented pingpong_count 6 to 0
1 received pingpong_count 7 from 0
1 sent and incremented pingpong_count 8 to 0
1 received pingpong_count 9 from 0
0 sent and incremented pingpong_count 9 to 1
0 received pingpong_count 10 from 1
1 sent and incremented pingpong_count 10 to 0
63
double t = MPI_Wtime();
int pingpong_count = 0;
int partner_rank = (get_comm_rank() + 1) % 2;
while (pingpong_count < PING_PONG_LIMIT) {
if (get_comm_rank() == pingpong_count % 2) {
// Увеличиваем счетчик перед отправкой
pingpong_count++;
MPI_Send(&pingpong_count, 1, MPI_INT, partner_rank, 0,
MPI_COMM_WORLD);
printf("%d sent and incremented ping_pong_count %d to %dn",
get_comm_rank(), pingpong_count, partner_rank);
} else {
MPI_Recv(&pingpong_count, 1, MPI_INT, partner_rank, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("%d received pingpong_count %d from %dn",
get_comm_rank(), pingpong_count, partner_rank);
}
}
}
t = MPI_Wtime() - t;
if (get_comm_rank() == 1)
printf("Ping pong execution time: %fn", t);
Пример: пинг-понг – вариант 2, с измерением времени
64
▪ Набор процессов организован в кольцо.
▪ Каждый процесс записывает свой ранг в целой переменной send_buf
▪ Каждый процесс отправляет переменную send_buf своему соседу справа.
▪ Каждый процесс получает значение от своего соседа слева и записывает его в
recv_buf, после чего подсчитывает сумму всех полученных значений.
▪ Передача сообщений выполняется до тех пор, пока процесс не получит значение
своего ранга, т.е. значение вернётся в точку старта.
▪ Каждый процесс, таким образом, расчитает сумму всех рангов.
Пример: передача информации по кольцу
0
3 1
2
65
Пример: передача информации по кольцу
rank
0
sendbuf
recvbufsum
0
rank
1
sendbuf
recvbufsum
0
rank
2
sendbuf
recvbufsum
0
Процесс 0
Процесс 1
Процесс 2
66
Пример: передача информации по кольцу
rank
0
sendbuf
0
recvbufsum
0
rank
1
sendbuf
1
recvbufsum
0
rank
2
sendbuf
2
recvbufsum
0
Процесс 0
Процесс 1
Процесс 2
1
1
1
67
Пример: передача информации по кольцу
rank
0
sendbuf
0
recvbufsum
0
rank
1
sendbuf
1
recvbufsum
0
rank
2
sendbuf
2
recvbufsum
0
Процесс 0
Процесс 1
Процесс 2
1
1
1
2
2
2
68
Пример: передача информации по кольцу
rank
0
sendbuf
0
recvbuf
2
sum
0
rank
1
sendbuf
1
recvbuf
0
sum
0
rank
2
sendbuf
2
recvbuf
1
sum
0
Процесс 0
Процесс 1
Процесс 2
1
1
1
2
2
2
3
3
3
69
Пример: передача информации по кольцу
rank
0
sendbuf
0
recvbuf
2
sum
2
rank
1
sendbuf
1
recvbuf
0
sum
0
rank
2
sendbuf
2
recvbuf
1
sum
1
Процесс 0
Процесс 1
Процесс 2
1
1
1
2
2
2
3
3
3
4
4
4
70
Пример: передача информации по кольцу
rank
0
sendbuf
2
recvbuf
2
sum
2
rank
1
sendbuf
0
recvbuf
0
sum
0
rank
2
sendbuf
1
recvbuf
1
sum
1
Процесс 0
Процесс 1
Процесс 2
1
1
1
2
2
2
3
3
3
4
4
4
5
5
5
71
Пример: передача информации по кольцу
rank
0
sendbuf
2
recvbuf
2
sum
2
rank
1
sendbuf
0
recvbuf
0
sum
0
rank
2
sendbuf
1
recvbuf
1
sum
1
Процесс 0
Процесс 1
Процесс 2
72
Пример: передача информации по кольцу
rank
0
sendbuf
1
recvbuf
1
sum
3
rank
1
sendbuf
2
recvbuf
2
sum
2
rank
2
sendbuf
0
recvbuf
0
sum
1
Процесс 0
Процесс 1
Процесс 2
2
2
2
3
3
3
4
4
4
5
5
5
73
Пример: передача информации по кольцу
rank
0
sendbuf
0
recvbuf
0
sum
3
rank
1
sendbuf
1
recvbuf
1
sum
3
rank
2
sendbuf
2
recvbuf
2
sum
3
Процесс 0
Процесс 1
Процесс 2
2
2
2
3
3
3
4
4
4
5
5
5
74
Пример: передача информации по кольцу
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
int dest = (rank + 1) % size;
int source = (rank - 1 + size) % size;
int send_buf = rank;
int recv_buf = 0;
int sum = 0;
do {
MPI_Send(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD);
printf("%d send %d to process %dn", rank, send_buf, dest);
MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
sum += recv_buf;
send_buf = recv_buf;
printf("%d received %d from process %d, sum %dn",
rank, recv_buf, source, sum);
} while (recv_buf != rank);
75
Пример: передача информации по кольцу
Process 2 send 2 to process 0
Process 1 send 1 to process 2
Process 2 received 1 from process 1, sum 1
Process 2 send 1 to process 0
Process 0 send 0 to process 1
Process 1 received 0 from process 0, sum 0
Process 1 send 0 to process 2
Process 2 received 0 from process 1, sum 1
Process 2 send 0 to process 0
Process 0 received 2 from process 2, sum 2
Process 0 send 2 to process 1
Process 0 received 1 from process 2, sum 3
Process 0 send 1 to process 1
Process 0 received 0 from process 2, sum 3
Process 1 received 2 from process 0, sum 2
Process 1 send 2 to process 2
Process 1 received 1 from process 0, sum 3
Process 2 received 2 from process 1, sum 3
76
Синхронная и асинхронная передача сообщений
1 Синхронная передача сообщений
Асинхронная передача сообщений2
MPI_Ssend
MPI_Bsend
MPI_Send
MPI_Recv
3 Тип отправки определяется библиотекой
77
Взаимная блокировка (дедлок)
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
int dest = (rank + 1) % size;
int source = (rank - 1 + size) % size;
int send_buf = rank;
int recv_buf = 0;
int sum = 0;
do {
MPI_Send(...);
printf("%d send %d to process %dn", rank, send_buf, dest);
MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
sum += recv_buf;
send_buf = recv_buf;
printf("%d received %d from process %d, sum %dn",
rank, recv_buf, source, sum);
} while (recv_buf != rank);
MPI_Ssend
MPI_Bsend
Дедлок!
78
Взаимная блокировка (дедлок)
MPI_Ssend
MPI_Recv
Процесс 0
MPI_Ssend
MPI_Recv
Процесс 1
P0
MPI_Ssend в отправителе будет
вызвана, но никогда не вернёт
выполнение, поскольку MPI_Recv не
может быть вызвана в процессе-
получателе.
P1
P2
P3
P4
При использовании синхронных
коммуникационных функций в примере
передачи сообщений по кольцу
произойдёт взаимная блокировка
79
Передача информации по кольцу: буферезированная отправка
int MPI_Bsend(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
▪ buf – адрес для начала отправляемого сообщения с количеством count
элементов типа datatype;
▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе
с сообщением; тэг может быть использован программой для разделения разных
типов сообщений;
▪ comm – коммуникатор, в котором находятся процессы;
▪ Пользователь должен подключить буфер достаточного размера с помощью
функции MPI_Buffer_attach.
int MPI_Buffer_attach(void *buffer, int size)
▪ присоединяет буфер размера size в памяти, начиная с адреса buffer, для
отправления сообщений в буферезированном режиме;
▪ только один буфер может быть присоединён к процессу;
▪ отсоединяет присоединённый буфер, возвращает адрес и размер буфера;
▪ операция блокируется до тех пор, пока все сообщения не будут переданы.
int MPI_buffer_detach(void *buffer, int *size)
80
Размер буфера при буферизированной отправке
▪ размер буфера должен быть равен суммарному размеру всех передаваемых
сообщений плюс MPI_BSEND_OVERHEAD для каждой функции Bsend;
▪ для расчета размера можно использовать MPI_Pack_size:
// Пример расчета размера буфера
MPI_Pack_size(20, MPI_INT, comm, &s1 );
MPI_Pack_size(40, MPI_FLOAT, comm, &s2 );
size = s1 + s2 + 2 * MPI_BSEND_OVERHEAD;
// Размер буфера должен быть не меньше, чем расчитанный size:
MPI_Buffer_attach(buffer, size);
MPI_Bsend(..., count = 20, datatype = MPI_INT, ...);
...
MPI_Bsend(..., count = 40, datatype = MPI_FLOAT, ...);
int size;
char *buf;
MPI_Buffer_attach(malloc(BUFSIZE), BUFSIZE);
// ...
MPI_Buffer_detach(&buf, &size);
// ...
MPI_Buffer_attach(buf, size);
▪ Пример присоединения буфера размера BUFSIZE:
81
Пример: передача информации по кольцу
// ...
int dest = (rank + 1) % size;
int source = (rank - 1 + size) % size;
int send_buf = rank, recv_buf = 0
int sum = 0;
int bufsize = 0;
char *buf = NULL;
MPI_Pack_size(1, MPI_INT, MPI_COMM_WORLD, &bufsize);
bufsize += MPI_BSEND_OVERHEAD;
MPI_Buffer_attach(malloc(bufsize), bufsize);
do {
MPI_Bsend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD);
printf("%d send %d to process %dn", rank, send_buf, dest);
MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
sum += recv_buf;
send_buf = recv_buf;
printf("%d received %d from process %d, sum %dn",
rank, recv_buf, source, sum);
} while (recv_buf != rank);
82
Неблокируемые коммуникации
MPI_Isend
MPI_Irecv
MPI_Test
MPI_Wait
1
2
3
▪ Неблокируемые операции позволяют совместить вычисления и коммуникации.
▪ Особенно актуально в системах, где обмены могут выполняться автономно
сетевым контроллером.
▪ Неблокируемая операция завершается ещё до того, как сообщение будет
скопировано в буфер отправки (получения).
▪ Операция копирования в буфер отправки (получения) может выполняться
параллельно с вычислениями.
▪ Неблокируемая функция отправки, как и обычная, может быть следующих видов:
стандартная, буферезированная, синхронная и по готовности.
83
Неблокируемые коммуникации
MPI_Isend
MPI_Irecv
MPI_Test
MPI_Wait
Выполнение неблокируемых операций разделяются на три фазы:
1. Инициализация неблокируемого обмена данными.
Немедленный (Immediate) возврат из функции (MPI_Isend, MPI_Irecv,
MPI_Ibcast, MPI_Ireduce, …).
2. Процесс, вызвавший функцию, выполняет какую-то полезную работу
(вычисления или другие коммуникации).
3. Ожидание завершения коммуникации (MPI_Wait) или периодическая
проверка завершения (MPI_Test).
1
2
3
84
Неблокируемые коммуникации: передача по кольцу
Реализация передачи по кольцу на основе неблокируемых обменов:
1. Инициализация неблокируемого обмена данными.
Инициализация функции отправки (MPI_Isend) сообщения
следующему процессу.
2. Процесс, вызвавший функцию, выполняет какую-то полезную работу.
Получение сообщения от правого процесса (MPI_Recv).
3. Ожидание завершения коммуникации (MPI_Wait) или периодическая
проверка завершения (MPI_Test).
P0
P1
P2
P3
P4
85
Реализация передачи по кольцу на основе неблокируемых обменов:
1. Инициализация неблокируемого обмена данными.
Инициализация функции получения (MPI_Irecv) сообщения от
предыдущего процесса.
2. Процесс, вызвавший функцию, выполняет какую-то полезную работу.
Отправка сообщения следующему процессу (MPI_Send).
3. Ожидание завершения коммуникации (MPI_Wait) или периодическая
проверка завершения (MPI_Test).
P0
P1
P2
P3
P4
Неблокируемые коммуникации: передача по кольцу
86
Неблокируемые дифференцированные обмены
int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm, MPI_Request *request)
▪ request – идентификатор, по которому можно обратиться и получить статус
операции или дождаться завершения её выполнения;
int MPI_Issend(void *buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm, MPI_Request *request)
int MPI_Ibsend(void *buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm, MPI_Request *request)
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source,
int tag, MPI_Comm comm, MPI_Request *request)
87
Неблокируемые дифференцированные обмены
int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status)
▪ request – идентификатор, по которому можно обратиться и получить статус
операции или дождаться завершения её выполнения;
▪ flag – равен true, если операция завершена;
▪ status – статус завершения операции (м.б. MPI_STATUS_IGNORE).
Проверка статуса выполнения неблокируемой операции:
int MPI_Wait(MPI_Request *request, MPI_Status *status)
Ожидание завершения неблокируемой операции:
88
Пеередача по кольцу на основе неблокируемых обменов
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
int dest = (rank + 1) % size;
int source = (rank - 1 + size) % size;
int send_buf = rank, recv_buf = 0;
int sum = 0;
MPI_Request request;
do {
MPI_Isend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD,
&request);
printf("%d send %d to process %dn", rank, send_buf, dest);
MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
sum += recv_buf;
send_buf = recv_buf;
printf("%d received %d from process %d, sum %dn",
rank, recv_buf, source, sum);
MPI_Wait(&request, MPI_STATUS_IGNORE);
} while (recv_buf != rank);
89
Пеередача по кольцу на основе неблокируемых обменов
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
int dest = (rank + 1) % size;
int source = (rank - 1 + size) % size;
int send_buf = rank, recv_buf = 0;
int sum = 0;
MPI_Request request;
do {
MPI_Issend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD,
&request);
printf("%d send %d to process %dn", rank, send_buf, dest);
MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
sum += recv_buf;
send_buf = recv_buf;
printf("%d received %d from process %d, sum %dn",
rank, recv_buf, source, sum);
MPI_Wait(&request, MPI_STATUS_IGNORE);
} while (recv_buf != rank);
90
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0
MPI_Recv
Процесс 1
msgsize
= X
Размер сообщения определяется
в процессе выполнения программы
и заранее известен только
отправителю.
const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
// Выбираем случайный размер сообщения
srand(time(NULL));
number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS;
// Отправляет сообщение случайного размера первому процессу
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// Получаем сообщение размера до MAX_NUMBERS от нулевого процесса
MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
&status);
// Определяем размер полученного сообщения и печатаем сообщение
MPI_Get_count(&status, MPI_INT, &number_amount);
printf("1 received %d numbers from 0. Message source = %d, "
"tag = %dn",
number_amount, status.MPI_SOURCE, status.MPI_TAG);
}
91
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0 Процесс 1
msgsize
= X MPI_Recv
const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
// Выбираем случайный размер сообщения
srand(time(NULL));
number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS;
// Отправляет сообщение случайного размера первому процессу
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// Получаем сообщение размера до MAX_NUMBERS от нулевого процесса
MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
&status);
// Определяем размер полученного сообщения и печатаем сообщение
MPI_Get_count(&status, MPI_INT, &number_amount);
printf("1 received %d numbers from 0. Message source = %d, "
"tag = %dn",
number_amount, status.MPI_SOURCE, status.MPI_TAG);
}
92
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0 Процесс 1
msgsize
= X
0 sent 56 numbers to 1
1 received 56 numbers from 0. Message source = 0, tag = 0
MPI_Recv
const int MAX_NUMBERS = 100;
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
// Выбираем случайный размер сообщения
srand(time(NULL));
number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS;
// Отправляет сообщение случайного размера первому процессу
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// Получаем сообщение размера до MAX_NUMBERS от нулевого процесса
MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
&status);
// Определяем размер полученного сообщения и печатаем сообщение
MPI_Get_count(&status, MPI_INT, &number_amount);
printf("1 received %d numbers from 0. Message source = %d, "
"tag = %dn",
number_amount, status.MPI_SOURCE, status.MPI_TAG);
}
93
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0 Процесс 1
msgsize
= X
Перед получением
необходимо выделить
буфер максимального
размера.
0 sent 56 numbers to 1
1 received 56 numbers from 0. Message source = 0, tag = 0
MPI_Recv
94
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0
MPI_Probe
MPI_Recv
Процесс 1
msgsize
= X
msgsize
Получатель сначала определяет
размер получаемого сообщения
(MPI_Probe), затем выделяет буфер
нужного размера
и после этого принимает сообщение
(MPI_Recv).
int numbers[MAX_NUMBERS];
int number_amount;
if (world_rank == 0) {
const int MAX_NUMBERS = 100;
// Выбираем случайный размер сообщения
// и отправляем 1 процессу
number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS;
MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
printf("0 sent %d numbers to 1n", number_amount);
} else if (world_rank == 1) {
MPI_Status status;
// Проверяем входящее сообщение и получаем его размер
MPI_Probe(0, 0, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_INT, &number_amount);
// Выделяем буфер необходимого размера и используем его для получения
int* number_buf = (int*) malloc(sizeof(int) * number_amount);
// Получаем сообщение размера number_buf от нулевого процесса
MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD,
&status);
printf("1 received %d numbers from 0", number_amount);
free(number_buf);
}
95
Пример: обмен сообщениями динамического размера
MPI_Send
Процесс 0
MPI_Probe
MPI_Recv
Процесс 1
msgsize
= X
msgsize
96
Пример: случайное блуждание
W
a b
S
▪ Даны границы области a, b и указатель W.
▪ Указатель W делает S случайных шагов произвольной длины вправо.
▪ Когда блуждатель доходит до конца области, он начинает с начала.
typedef struct { // Тип данных указателя
int loc; // текущая позиция
int steps; // оставшееся число шагов
} walker_t;
walker.loc = 0;
walker.steps = (rand() / (float) RAND_MAX) * max_walk_size
while (walker->steps > 0) {
if (walker->loc == domain_size) // Если вышли за пределы области
walker->loc = 0; // то вернулись в начало области
} else {
walker->steps--;
walker->loc++;
}
}
Схема распараллеливания алгоритма случайного блуждания:
1. Область разбивается на равные части по числу процессов.
2. Указатель выполняет шаги. Как только он достигает границы области, он передаёт
состояние указателя соседнему процессу.
3. Процесс, получив состояние указателя, продолжает его перемещение до нужного
количества шагов.
97
Случайное блуждание: распараллеливание
a b
P0 P1 P2 P3
0 4 5 9 10 14 15 20
Процессы
b
0 4 5 9 10 14 15 20
Процессы
W
P0 P1 P2 P3
98
Случайное блуждание: распараллеливание
▪ Каждый процесс определяет границы своей области.
▪ Каждый процесс инициализирует N указателей, которые начинают движение с
первого значения их области.
▪ Каждый указатель W имеет два целых значения: p – позиция указателя, s – число
шагов, которые остаётся пройти.
▪ Указатели начинают обход по области и передаются другим процессам до тех пор,
пока они не закончат перемещение.
▪ Процесс завершается, когда все указатели завершили движение.
b
0 4 5 9 10 14 15 20
Процессы
W0
(p0
, s0
)
P0 P1 P2 P3
W1
(p1
, s1
) W2
(p2
, s2
) W3
(p3
, s3
)
99
Случайное блуждание: декомпозиция области
void split_domain(int domain_size, int world_rank,
int world_size, int* subdomain_start,
int* subdomain_size) {
if (world_size > domain_size) {
// Размер области должен быть больше числа процессов
MPI_Abort(MPI_COMM_WORLD, 1);
}
*subdomain_start = domain_size / world_size * world_rank;
*subdomain_size = domain_size / world_size;
if (world_rank == world_size - 1) {
// Оставшуюся часть области передать последнему процессу
*subdomain_size += domain_size % world_size;
}
}
100
Случайное блуждание: указатели (walkers)
// Тип данных указателя
typedef struct {
int loc; // текущая позиция
int steps; // оставшееся число шагов
} walker_t;
// Инициализация вектора указателей по заданной подобласти
void init_walkers(int nwalkers_per_proc, int max_walk_size,
int subdomain_start, int subdomain_size,
vector<walker_t>* incoming_walkers) {
walker_t walker;
for (int i = 0; i < nwalkers_per_proc; i++) {
// Начальное положение на подобласти
walker.loc = subdomain_start;
// Инициализация случайного количества шагов
walker.steps = (rand() / (float) RAND_MAX) * max_walk_size;
incoming_walkers->push_back(walker);
}
}
101
Случайное блуждание: функция блуждания
// Функция блуждания, которая реализует перемещение указателя
// до завершения всех шагов. Если указатель упёрся в границу подобласти,
// он добавляется в вектор outgoing_walkers
void walk(walker_t* walker, int subdomain_start, int subdomain_size,
int domain_size, vector<walker_t>* outgoing_walkers) {
while (walker->num_steps_left_in_walk > 0) {
if (walker->location == subdomain_start + subdomain_size) {
if (walker->location == domain_size) {
// Если указатель достиг границы глобальной области,
// он переходит на начало
walker->location = 0;
}
// Если указатель достиг границы локальной области,
// он добавляется в массив outgoing_walker
outgoing_walkers->push_back(*walker);
break;
} else {
walker->num_steps_left_in_walk--;
walker->location++;
}
}
}
102
Отправка указателей, достигших границы локальной подобласти
// Отправка вектора указателей следующему процессу
void send_outgoing_walkers(vector<walker_t>* outgoing_walkers,
int world_rank, int world_size) {
// Отправить массив указателей следующему процессу.
// Последний процесс отправляет сообщение нулевому.
MPI_Send((void*)outgoing_walkers->data(),
outgoing_walkers->size() * sizeof(Walker), MPI_BYTE,
(world_rank + 1) % world_size, 0, MPI_COMM_WORLD);
// Очистить массив исходящих указателей
outgoing_walkers->clear();
}
103
Получение указателей, достигших границы локальной подобласти
void receive_incoming_walkers(vector<walker_t>* incoming_walkers,
int world_rank, int world_size) {
MPI_Status status;
// Получение сообщения неизвестного размера от предыдущего процесса.
// Процесс 0 полчает от последнего процесса.
int incoming_rank =
(world_rank == 0) ? world_size - 1 : world_rank - 1;
MPI_Probe(incoming_rank, 0, MPI_COMM_WORLD, &status);
// Подготовить буфер для получения: изменить его размер на размер
// принимаемого сообщения.
int incoming_walkers_size;
MPI_Get_count(&status, MPI_BYTE, &incoming_walkers_size);
incoming_walkers->resize(
incoming_walkers_size / sizeof(Walker));
MPI_Recv((void*)incoming_walkers->data(), incoming_walkers_size,
MPI_BYTE, incoming_rank, 0, MPI_COMM_WORLD,
MPI_STATUS_IGNORE);
}
104
Случайное блуждание. MPI-версия
1. Инициализация указателей.
2. Выполнение функции walk указателями.
3. Все процессы отправляют вектор outgoing_walkers.
4. Все процессы принимают новые указатели в вектор incoming_walkers.
5. Повторять шаги до тех пор, пока все указатели не закончат перемещение.
split_domain(domain_size, world_rank, world_size, &subdomain_start,
&subdomain_size);
// Проинициализировать указатели в локальной подобласти
init_walkers(num_walkers_per_proc, max_walk_size,
subdomain_start, subdomain_size, &incoming_walkers);
while (!all_walkers_finished) {
// Обработать все входящие указатели
for (int i = 0; i < incoming_walkers.size(); i++) {
walk(&incoming_walkers[i], subdomain_start, subdomain_size,
domain_size, &outgoing_walkers);
}
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
}
2
105
Случайное блуждание. MPI-версия – дедлок!
split_domain(domain_size, world_rank, world_size, &subdomain_start,
&subdomain_size);
// Проинициализировать указатели в локальной подобласти
init_walkers(num_walkers_per_proc, max_walk_size,
subdomain_start, subdomain_size, &incoming_walkers);
while (!all_walkers_finished) {
// Обработать все входящие указатели
for (int i = 0; i < incoming_walkers.size(); i++) {
walk(&incoming_walkers[i], subdomain_start, subdomain_size,
domain_size, &outgoing_walkers);
}
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
}
b
0 4 5 9 10 14 15 20
P0 P1 P2 P3
a
1 1 1 1
1
2 22
2
106
Случайное блуждание. Способы решения проблемы дедлока
b
0 4 5 9 10 14 15 20
P0 P1 P2 P3
a
1. Использование всегда буферезированной функции отправки MPI_Bsend.
2. Неблокируемые коммуникации MPI_Isend, MPI_Irecv.
3. Изменить порядок отправки / получения сообщений так, чтобы каждому
send соответствовал recv. Для этого можно
a. для чётных процессов указать порядок сначала send, потом recv
b. для нечётных процессов сначала recv, потом send
212 1 212 1
2
107
Случайное блуждание. Решение проблемы дедлока
b
0 4 5 9 10 14 15 20
P0 P1 P2 P3
a
12 1
split_domain(...);
init_walkers(...);
while (!all_walkers_finished) {
for (int i = 0; i < incoming_walkers.size(); i++)
walk(&incoming_walkers[i], subdomain_start, subdomain_size,
domain_size, &outgoing_walkers);
if (rank % 2 == 0)
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
} else {
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
}
1
1
2
2
212 1
108
Случайное блуждание. Определение завершения
split_domain(...);
init_walkers(...);
// Определяем максимальное количество обменов, необходимых для завершения
// блужданий всех указателей.
int maximum_sends_recvs =
max_walk_size / (domain_size / world_size) + 1;
for (int m = 0; m < maximum_sends_recvs; m++) {
for (int i = 0; i < incoming_walkers.size(); i++) {
walk(&incoming_walkers[i], subdomain_start, subdomain_size,
domain_size, &outgoing_walkers);
}
if (rank % 2 == 0)
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
} else {
receive_incoming_walkers(&incoming_walkers, world_rank, world_size);
send_outgoing_walkers(&outgoing_walkers, world_rank, world_size);
}
109
Случайное блуждание. Пример выполнения
8 processes
domain_size = 100 max_walk_size = 1000 num_walkers_per_proc = 10
Process 0 initiated 10 walkers in subdomain 0 - 11
Process 0 sending 10 outgoing walkers to process 1
Process 3 initiated 10 walkers in subdomain 36 - 47
Process 3 sending 10 outgoing walkers to process 4
Process 4 initiated 10 walkers in subdomain 48 - 59
Process 4 sending 10 outgoing walkers to process 5
...
Process 7 received 10 incoming walkers
Process 7 sending 10 outgoing walkers to process 0
Process 0 received 10 incoming walkers
Process 0 sending 10 outgoing walkers to process 1
...
Process 6 sending 0 outgoing walkers to process 7
Process 6 received 0 incoming walkers
Process 6 done
Process 7 received 2 incoming walkers
Process 7 sending 2 outgoing walkers to process 0
...
Process 6 sending 0 outgoing walkers to process 7
Process 6 received 0 incoming walkers
Process 6 done
Process 7 received 2 incoming walkers
Process 7 sending 2 outgoing walkers to process 0
Process 7 done

More Related Content

PDF
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
PDF
Administration Linux ( PDFDrive ).pdf
PPTX
04 pemodelan spk
PDF
Best Practices in Qt Quick/QML - Part 1 of 4
 
PDF
Dev112 let's calendar that
PDF
[PBO] Pertemuan 11 - GUI Java Desktop
PPT
Chuong 1 - CSDL phân tán
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Administration Linux ( PDFDrive ).pdf
04 pemodelan spk
Best Practices in Qt Quick/QML - Part 1 of 4
 
Dev112 let's calendar that
[PBO] Pertemuan 11 - GUI Java Desktop
Chuong 1 - CSDL phân tán

What's hot (7)

PPT
Chuong 3 windows forms
PDF
PyQGIS 개발자 쿡북(PyQGIS Developer Cookbook) 한국어 판
PDF
Rpl 012 - perancangan berorientasi objek
DOCX
Tutorial lanjutan java netbeans 8 : Create Read Update Delete
PDF
Phân tích kiến trúc và nguyên lý làm việc của bộ nhớ RAM chuẩn DDRAM
PDF
Đề tài: Xây dựng hệ thống thi trắc nghiệm qua mạng LAN, 9đ
PPTX
ระบบปฏิบัติการ Linux
Chuong 3 windows forms
PyQGIS 개발자 쿡북(PyQGIS Developer Cookbook) 한국어 판
Rpl 012 - perancangan berorientasi objek
Tutorial lanjutan java netbeans 8 : Create Read Update Delete
Phân tích kiến trúc và nguyên lý làm việc của bộ nhớ RAM chuẩn DDRAM
Đề tài: Xây dựng hệ thống thi trắc nghiệm qua mạng LAN, 9đ
ระบบปฏิบัติการ Linux
Ad

Viewers also liked (12)

PDF
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
PDF
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
PDF
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
PDF
Семинар 11. Параллельное программирование на MPI (часть 4)
PDF
ПВТ - весна 2015 - Лекция 0. Описание курса
PDF
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
PDF
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
PDF
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
PDF
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
PDF
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
PDF
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
PDF
Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 1. Многопоточное программирование на OpenMP (часть 1)
Семинар 11. Параллельное программирование на MPI (часть 4)
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхрониза...
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
Семинар 9. Параллельное программирование на MPI (часть 2)
Ad

Similar to Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены (20)

PDF
Семинар 8. Параллельное программирование на MPI (часть 1)
PDF
Стандарт MPI (Message Passing Interface)
PDF
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
PDF
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, paral...
PPT
кластеры и суперкомпьютеры
PPTX
Введение в MPI
PDF
CUDA-Aware MPI notes
PDF
Пространственно-распределенная мультикластерная вычислительная система: архит...
PPT
20090721 hpc exercise2
PPT
Bgp методякоби
PDF
О.В.Сухорослов "Параллельное программирование. Часть 2"
PDF
Семинар 10. Параллельное программирование на MPI (часть 3)
PDF
20140420 parallel programming_kalishenko_lecture10
PDF
Лекция 1: Архитектурно-ориентированная оптимизация программного обеспечения (...
PPTX
Функции передачи сообщений MPI
PDF
CUDA-Aware MPI
PPTX
Multiprocessor Programming Intro (lecture 1)
PPT
022
PDF
Технология OpenMP
PPS
Prezent
Семинар 8. Параллельное программирование на MPI (часть 1)
Стандарт MPI (Message Passing Interface)
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, Paral...
Лекция 5. Основы параллельного программирования (Speedup, Amdahl's law, paral...
кластеры и суперкомпьютеры
Введение в MPI
CUDA-Aware MPI notes
Пространственно-распределенная мультикластерная вычислительная система: архит...
20090721 hpc exercise2
Bgp методякоби
О.В.Сухорослов "Параллельное программирование. Часть 2"
Семинар 10. Параллельное программирование на MPI (часть 3)
20140420 parallel programming_kalishenko_lecture10
Лекция 1: Архитектурно-ориентированная оптимизация программного обеспечения (...
Функции передачи сообщений MPI
CUDA-Aware MPI
Multiprocessor Programming Intro (lecture 1)
022
Технология OpenMP
Prezent

More from Alexey Paznikov (20)

PDF
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
PDF
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
PDF
Лекция 4. Производные типы данных в стандарте MPI
PDF
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
PDF
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
PDF
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
PDF
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
PDF
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
PDF
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
PDF
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
PDF
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
PDF
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
PDF
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
PDF
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
PDF
ПВТ - осень 2014 - лекция 1а - Описание курса
PDF
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
PPTX
ТФРВС - весна 2014 - лекция 11
PPTX
ТФРВС - весна 2014 - лекция 10
PPTX
ТФРВС - весна 2014 - лекция 9
PPTX
ТФРВС - весна 2014 - лекция 8
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 4. Производные типы данных в стандарте MPI
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...
ПВТ - осень 2014 - лекция 1а - Описание курса
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 8

Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены

  • 1. Лекция 1. Базовые понятия стандарта MPI. Дифференцированные обмены Пазников Алексей Александрович Параллельные вычислительные технологии СибГУТИ (Новосибирск) Осенний семестр 2015 www: cpct.sibsutis.ru/~apaznikov/teaching q/a: piazza.com/sibsutis.ru/fall2015/pct2015fall
  • 3. Вычислительные системы с распределённой памятью 3 CPU 1 Кэш CPU 3 Кэш CPU n Кэш CPU 1 Кэш CPU 2 Кэш CPU 3 Кэш CPU n Кэш Общая память (shared memory) Общая память (shared memory) Коммуникационная сеть (Interconnect, Communication network) Распределённая вычислительная система (ВС) (distributed computer system) представляет собой совокупность вычислительных узлов, взаимодействующих через коммуникационную сеть (Gigabit Ethernet, InfiniBand, Cray Gemeni, Fujitsu Tofu, etc) ▪ Каждый узел включает в себя несколько процессоров (CPU 1, …, CPU n), взаимодействующих через общую память (shared memory). CPU 2 Кэш
  • 4. CPU 2CPU 1 Вычислительные системы с распределённой памятью 4 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Общая память (shared memory) Коммуникационная сеть (Interconnect, Communication network) Распределённая вычислительная система (ВС) (distributed computer system) представляет собой совокупность вычислительных узлов, взаимодействующих через коммуникационную сеть (Gigabit Ethernet, InfiniBand, Cray Gemeni, Fujitsu Tofu, etc) ▪ Каждый узел включает в себя несколько процессоров (CPU1...CPUn), взаимодействующих через общую память (shared memory). ▪ Процессоры могут быть многоядерными (multicore) и ядра могут поддерживать одновременное выполнение нескольких потоков (SMT, Hyper-threading). Кэш L2 Кэш L2 CPU 2CPU 1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Общая память (shared memory) Кэш L2 Кэш L2
  • 5. CPU 2CPU 1 Вычислительные системы с распределённой памятью 5 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Общая память (shared memory) Коммуникационная сеть (Interconnect, Communication network) ▪ Instruction Level Parallelism (ILP) – параллелизм уровня инструкций. Суперскалярный конвейер, внеочередное выполнение команд. SSE/AVX, AltivEC, ARM NEON ▪ Thread Level Parallelism (TLP) – параллелизм потоков. POSIX Threads, OpenMP, C++- threads, Cilk, Intel TBB, GPGPU (OpenCL, CUDA, OpenACC) ▪ Process Level Parallelism (PLP) (message passing) – параллелизм уровня процессов, интерфейс обмена сообщениями. MPI, OpenSHMEM, PGAS (Cray Chapel, IBM X10, UPC) Кэш L2 Кэш L2 CPU 2CPU 1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Ядро 1 Кэш L1 Ядро 2 Кэш L1 Общая память (shared memory) Кэш L2 Кэш L2 ILP ILP ILPILP ILP ILPILPILP TLP / PLP TLP / PLP TLP / PLP TLP / PLP TLP / PLP TLP / PLP PLP
  • 6. Вычислительный узел 1 CPU 1 Вычислительные кластеры 6 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Головной узел CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Сервисная сеть (NFS, DNS, DHCP, ssh) Вычислительная сеть (MPI) Вычислительные кластеры (computer clusters) строятся на базе свободно доступных компонентов. ▪ Вычислительные узлы ▪ Головной узел (frontend) ▪ Вычислительная сеть ▪ Сервисная сеть ▪ Подсистема хранения данных ▪ Подсистема бесперебойного электропитания ▪ Система охлаждения ▪ Программное обеспечение
  • 7. Рейтинги суперкомпьютеров: TOP500 7 № Место Система Число ядер Макс. произв., TFLOPS Пиковая произв., TFLOPS Эл. эн., KW 1 National Super Computer Center in Guangzhou China Tianhe-2 (MilkyWay-2) - TH-IVB- FEP Cluster, Intel Xeon E5-2692 12C 2.200GHz, TH Express-2, Intel Xeon Phi 31S1P, NUDT 3,120,000 33,862.7 54,902.4 17,808 2 DOE/SC/Oak Ridge National Laboratory United States Titan - Cray XK7 , Opteron 6274 16C 2.200GHz, Cray Gemini interconnect, NVIDIA K20x Cray Inc. 560,640 17,590.0 27,112.5 8,209 3 DOE/NNSA/LLNL United States Sequoia - BlueGene/Q, Power BQC 16C 1.60 GHz, Custom IBM 1,572,864 17,173.2 20,132.7 7,890 4 RIKEN Japan K computer, SPARC64 VIIIfx 2.0 GHz, Tofu interconnect Fujitsu 705,024 10,510.0 11,280.4 12,660 5 DOE/SC/Argonne National Laboratory United States Mira - BlueGene/Q, Power BQC 16C 1.60GHz, Custom IBM 786,432 8,586.6 10,066.3 3,945
  • 8. Рейтинги суперкомпьютеров: TOP500 8 Архитектурные особенности современных суперкомпьютеров: ▪ Большемасштабность: более 1000000 процессорных ядер ▪ Многоядерные процессоры с количеством ядер более 10 на процессор ▪ Высокое энергопотребление: несколько MW ▪ Коммуникационная сеть: Infiniband, Gigabit Ethernet, 10 Gigabit Ethernet, Cray Gemini, TH Express, Tofu Interconnect ▪ Процессоры: Intel, IBM Power, AMD Opteron, … ▪ Гетерогенные системы: наличие ускорителей NVIDIA GPU, Intel Xeon Phi, Tilera Tile-GX, PLD-accelerators, ▪ Операционные системы GNU/Linux (преимущественно), IBM AIX и др.
  • 10. Стандарт MPI 10 Message Passing Interface (MPI, интерфейс передачи сообщений) – стандарт программного интерфейса коммуникационных функций для создания переносимых параллельных программ в модели передачи сообщений (message passing). ▪ Стандарт реализован в MPI-библиотеках MPICH, Open MPI, Intel MPI и т.д. ▪ MPI появился в 1991 и был предложен группой учёных. С тех пор появился семинар по стандарту Workshop on Standards for Message passing in a Distributed Memory environment и сформирован комитет по стандартизации. Создан MPI- форум. ▪ MPI определяет синтаксис и семантику функций библиотек передачи сообщений для языков Fortran и C. ▪ Является стандартным средством для организации взаимодействия процессов в параллельных программах, выполняющихся на распределённых ВС. ▪ Обеспечивается переносимость программ между разными архитектурами процессоров (Cray, Intel, IBM, NEC, …) ▪ Последний стандарт MPI-3.1. вышел 4 июня 2015 г.
  • 11. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 11 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 ▪ MPI-программы включают N параллельно работающих процессов ▪ Каждый процесс назначается на отдельное процессорное ядро ▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N P1 P2 P3 P4 P5 ▪ Одна MPI-программа из 6 процессов (программа ранга 6)
  • 12. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 12 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 ▪ MPI-программы включают N параллельно работающих процессов ▪ Каждый процесс назначается на отдельное процессорное ядро ▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N P1 P2 P3 P4 P5 ▪ Четыре программы рангов 6, 1, 3, 2 ▪ Одна программа может быть запущена с разным количеством процессов P0 P0 P1 P2 P0 P1
  • 13. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 13 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро ▪ MPI-программы включают N параллельно работающих процессов ▪ Каждый процесс назначается на отдельное процессорное ядро ▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N P0 P1 P2 P3 P4 P5 if (myrank == 0) { goright(); } else if (myrank == 1) { goleft(); } Каждый процесс выполняет свою отдельную подпрограмму
  • 14. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 14 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро ▪ MPI-программы включают N параллельно работающих процессов ▪ Каждый процесс назначается на отдельное процессорное ядро ▪ Каждый процесс имеет уникальный ранг (номер процесса) от 0 до N Каждый процесс выполняет свою отдельную подпрограмму P0 P1 P2 P3 P4 P5 if (myrank == 0) { goright(); } else if (myrank == 1) { goleft(); } else { stand_and_wait(); }
  • 15. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 15 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 ▪ Процессы взаимодействуют между собой путём обмена сообщениями
  • 16. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 16 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 ▪ Процессы взаимодействуют между собой путём обмена сообщениями ▪ Или путём выполнений функций одностороннего доступа к памяти
  • 17. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 17 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 ▪ Коммуникатор в MPI это группа процессов. ▪ Все процессы MPI-программы формируют коммуникатор MPI_COMM_WORLD, который предопределён в файле mpi.h. ▪ Каждый процесс имеет свой ранг в пределах коммуникатора (начинающийся с 0 и заканчивающийся на commsize – 1) MPI_COMM_WORLD
  • 18. Вычислительный узел 1 CPU 1 Основные понятия MPI-программ 18 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 ▪ Получить ранг процесса можно с помощью функции MPI_Comm_rank MPI_COMM_WORLD int MPI_Comm_rank(MPI_Comm comm, int *rank); int MPI_Comm_size(MPI_Comm comm, int *size); ▪ Количество процессов в коммуникаторе
  • 19. 19 #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { int size, rank, name_len; // Проинициализировать библиотеку MPI MPI_Init(NULL, NULL); // Получить число процессов MPI_Comm_size(MPI_COMM_WORLD, &size); // Получить ранг процесса MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Получить имя процесса char procname[MPI_MAX_PROCESSOR_NAME]; MPI_Get_processor_name(processor_name, &name_len); // Напечатать сообщения каждым процессом printf("Hello world from processor %s, rank %d" " out of %d processorsn", processor_name, world_rank, world_size); // Завершить среду MPI MPI_Finalize(); } Заголовочный файл mpi.h MPI-функции имеют префикс MPI_ Инициализация среды MPI Завершение среды MPI Структура MPI-программы. “hello world”
  • 20. Структура MPI-программы. “hello world” 20 #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { int size, rank, name_len, rc; // Проинициализировать библиотеку MPI rc = MPI_Init(NULL, NULL); if (rc == MPI_SUCCESS) { // Получить число процессов MPI_Comm_size(MPI_COMM_WORLD, &size); // Получить ранг процесса MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Получить имя процесса char procname[MPI_MAX_PROCESSOR_NAME]; int name_len; MPI_Get_processor_name(processor_name, &name_len); // Напечатать сообщения каждым процессом printf(...); // Завершить среду MPI MPI_Finalize(); } else { fprintf(stderr, "Can't initialize MPI!n"); } } Заголовочный файл mpi.h MPI-функции имеют префикс MPI_ MPI-функции возвращают MPI_SUCCESS в случае успеха и код ошибки в противном случае
  • 21. Структура MPI-программы. “hello world” 21 #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { int size, rank, name_len, rc; // Проинициализировать библиотеку MPI rc = MPI_Init(NULL, NULL); if (rc == MPI_SUCCESS) { // Получить число процессов MPI_Comm_size(MPI_COMM_WORLD, &size); // Получить ранг процесса MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Получить имя процесса char procname[MPI_MAX_PROCESSOR_NAME]; int name_len; MPI_Get_processor_name(processor_name, &name_len); // Напечатать сообщения каждым процессом printf(...); if (rank == 0) printf("hello world!n"); // Завершить среду MPI MPI_Finalize(); } Заголовочный файл mpi.h Только нулевой процесс печатает сообщение “hello world”
  • 22. Компиляция MPI-программ 22 $ mpicc -Wall -O2 -o prog prog.c Программа на С Программа на С++ $ mpicxx -Wall -O2 -o prog prog.c Программа на Fortran $ mpif90 -Wall -O2 -o prog prog.c
  • 23. Запуск MPI-программ на кластере (система TORQUE) 23 #PBS -N mpi_hello #PBS -l nodes=2:ppn=8 #PBS -j oe cd $PBS_O_WORKDIR mpiexec ./hello Паспорт задачи (task.job) для запуска на двух узлах с использованием восьми ядер с каждого узла $ qsub task.job 1849 $ qstat Job ID Name User Time Use S Queue ----------------- ------------ ------- --------- - ----- 1849 mpi_hello ap 00:00:00 R debug Запуск задачи на кластере
  • 24. Запуск MPI-программ на кластере (система TORQUE) 24 $ qstat Job ID Name User Time Use S Queue ----------------- ------------ ------- --------- - ----- 1849 mpi_hello ap 00:00:00 C debug $ cat mpi_hello.o1849 Hello, MPI World! Process 8 of 16 on node cn1. Hello, MPI World! Process 9 of 16 on node cn1. Hello, MPI World! Process 10 of 16 on node cn1. Hello, MPI World! Process 11 of 16 on node cn1. Hello, MPI World! Process 12 of 16 on node cn1. Hello, MPI World! Process 0 of 16 on node cn2. Hello, MPI World! Process 3 of 16 on node cn2. Hello, MPI World! Process 4 of 16 on node cn2. Hello, MPI World! Process 5 of 16 on node cn2. Hello, MPI World! Process 6 of 16 on node cn2. Hello, MPI World! Process 7 of 16 on node cn2. Hello, MPI World! Process 13 of 16 on node cn1. Hello, MPI World! Process 14 of 16 on node cn1. Hello, MPI World! Process 15 of 16 on node cn1. Hello, MPI World! Process 1 of 16 on node cn2. Hello, MPI World! Process 2 of 16 on node cn2. Запуск задачи на кластере
  • 25. Вычислительный узел 1 CPU 1 25 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 3 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Головной узел CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Сервисная сеть (NFS, DNS, DHCP, ssh) Вычислительная сеть (MPI) Выполнение MPI-программы на кластере qsub task.job Запуск MPI-программы на кластере (что происходит при выполнении qsub task.job)
  • 26. Вычислительный узел 1 CPU 1 26 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 3 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Головной узел CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Сервисная сеть (NFS, DNS, DHCP, ssh) Вычислительная сеть (MPI) Выполнение MPI-программы на кластере qsub task.job Запуск MPI-программы на кластере (что происходит при выполнении qsub task.job) 1. TORQUE выделят подсистему процессорных ядер по паспорту задачи.
  • 27. Вычислительный узел 1 CPU 1 27 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 2 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 3 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Головной узел CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Сервисная сеть (NFS, DNS, DHCP, ssh) Вычислительная сеть (MPI) Выполнение MPI-программы на кластере mpiexec ./prog P0 P1 P2 P3 P4 P5 prog prog prog prog prog prog ssh cn2 ssh cn3 Запуск MPI-программы на кластере (что происходит при выполнении qsub task.job) 1. TORQUE выделят подсистему процессорных ядер по паспорту задачи. 2. mpiexec (скрипт) заходит по ssh на узлы, на которых выделена подсистема и запускает одинаковые экземпляры MPI- программы.
  • 28. Подсчет количества простых чисел (последовательная версия) 28 int is_prime_number(int n) { int limit = sqrt(n) + 1; for (int i = 2; i <= limit; i++) { if (n % i == 0) return 0; } return (n > 1) ? 1 : 0; } int count_prime_numbers(int a, int b) { int nprimes = 0; if (a <= 2) { nprimes = 1; // Посчитать '2' как простое a = 2; } if (a % 2 == 0) a++; // Сместить 'a' до нечётного числа // Цикл по нечётным числам: a, a + 2, a + 4, ... , b for (int i = a; i <= b; i += 2) if (is_prime_number(i)) nprimes++; return nprimes; } Определяет, является ли число n простым Подсчитывает количество простых чисел в интервале [a, b]
  • 29. Подсчет количества простых чисел (MPI версия) 29 int main(int argc, char **argv) { MPI_Init(&argc, &argv); // Запустить последовательную версию double tserial = 0; if (get_comm_rank() == 0) tserial = run_serial(); // Дождаться завершения выполнения последовательной версиии // нулевым процессом MPI_Barrier(MPI_COMM_WORLD); // Запустить параллельную версию double tparallel = run_parallel(); if (get_comm_rank() == 0) { printf("Count prime numbers on [%d, %d]n", a, b); printf("Execution time (serial): %.6fn", tserial); printf("Execution time (parallel): %.6fn", tparallel); printf("Speedup (processes %d): %.2fn", get_comm_size(), tserial / tparallel); } MPI_Finalize(); return 0; }
  • 30. Подсчет количества простых чисел (MPI версия) 30 double run_serial() { double t = MPI_Wtime(); int n = count_prime_numbers(a, b); t = MPI_Wtime() - t; printf("Result (serial): %dn", n); return t; } double run_parallel() { double t = MPI_Wtime(); int n = count_prime_numbers_par(a, b); t = MPI_Wtime() - t; printf("Process %d/%d execution time: %.6fn", get_comm_rank(), get_comm_size(), t); if (get_comm_rank() == 0) printf("Result (parallel): %dn", n); return t; } MPI-функция измерения временных интервалов Получение ранга процесса и количества процессов
  • 31. Подсчет количества простых чисел (MPI версия) 31 int get_comm_rank() { int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); return rank; } int get_comm_size() { int size; MPI_Comm_size(MPI_COMM_WORLD, &size); return size; }
  • 32. Подсчет количества простых чисел (MPI версия) 32 void get_chunk(int a, int b, int commsize, int rank, int *lb, int *ub) { int n = b - a + 1; int q = n / commsize; if (n % commsize) q++; int r = commsize * q - n; // Расчитать размер порции данных для процесса int chunk = q; if (rank >= commsize - r) chunk = q - 1; // Определить начало порции для процесса *lb = a; if (rank > 0) { // Подсчитать сумму предыдущих порций if (rank <= commsize - r) *lb += q * rank; else *lb += q * (commsize - r) + (q - 1) * (rank - (commsize - r)); } *ub = *lb + chunk - 1; }
  • 33. Подсчет количества простых чисел (MPI версия) 33 int count_prime_numbers_par(int a, int b) { int nprimes = 0; int lb, ub; get_chunk(a, b, get_comm_size(), get_comm_rank(), &lb, &ub); if (lb <= 2) { nprimes = 1; // Посчитать '2' как простое lb = 2; } if (lb % 2 == 0) lb++; // Сместить 'lb' до нечётного числа // Цикл по нечётным числам: a, a + 2, a + 4, ... , b for (int i = lb; i <= ub; i += 2) { if (is_prime_number(i)) nprimes++; int nprimes_global; MPI_Reduce(&nprimes, &nprimes_global, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); return nprimes_global; } Подсчитать сумму простых чисен, найденных всеми процессами
  • 34. Определение ускорения параллельной программы 34 Process 1/8 execution time: 0.641697 Process 3/8 execution time: 0.915595 Process 2/8 execution time: 0.915649 Process 5/8 execution time: 1.104982 Process 7/8 execution time: 1.267695 Process 6/8 execution time: 1.267713 Process 4/8 execution time: 1.267691 Result (serial): 664579 Process 0/8 execution time: 0.390985 Result (parallel): 664579 Count prime numbers on [1, 10000000] Execution time (serial): 7.310900 Execution time (parallel): 0.390985 Speedup (processes 8): 18.70 Проблема 1 Дисбаланс загрузки процессов Проблема 2 Неверно подсчитанное ускорение
  • 35. Подсчет количества простых чисел (MPI версия) 35 int count_prime_numbers_par(int a, int b) { int nprimes = 0; int lb, ub; get_chunk(a, b, get_comm_size(), get_comm_rank(), &lb, &ub); if (lb <= 2) { nprimes = 1; // Посчитать '2' как простое lb = 2; } if (lb % 2 == 0) lb++; // Сместить 'lb' до нечётного числа // Цикл по нечётным числам: a, a + 2, a + 4, ... , b for (int i = lb; i <= ub; i += 2) { if (is_prime_number(i)) nprimes++; int nprimes_global; MPI_Reduce(&nprimes, &nprimes_global, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); return nprimes_global; } Проблема 1 Неравномерно загрузка процессов Процесс 0: 0, 1, 2, 3 Процесс 1: 4, 5, 6, 7 Процесс 3: 8, 9, 10, 11
  • 36. Подсчет количества простых чисел (MPI версия) 36 int main(int argc, char **argv) { MPI_Init(&argc, &argv); // Запустить последовательную версию double tserial = 0; if (get_comm_rank() == 0) tserial = run_serial(); // Запустить параллельную версию double tparallel = run_parallel(); if (get_comm_rank() == 0) { printf("Count prime numbers on [%d, %d]n", a, b); printf("Execution time (serial): %.6fn", tserial); printf("Execution time (parallel): %.6fn", tparallel); printf("Speedup (processes %d): %.2fn", get_comm_size(), tserial / tparallel); } MPI_Finalize(); return 0; } Проблема 2 Неверно подсчитанное ускорение s = t1 / tp tp – время процесса 0 За tp необходимо принять наибольшее время выполнения процессов
  • 37. Подсчет количества простых чисел (MPI версия) 37 double run_parallel() { double t = MPI_Wtime(); int n = count_prime_numbers_par(a, b); t = MPI_Wtime() - t; printf("Process %d/%d execution time: %.6fn", get_comm_rank(), get_comm_size(), t); if (get_comm_rank() == 0) printf("Result (parallel): %dn", n); // Собираем в процессе 0 максимальное из времён выполнения double tmax; MPI_Reduce(&t, &tmax, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD); return tmax; } Решение проблемы 2 s = t1 / (max(t0 , t1 , …, tn )) Определить максимальное среди всех времён выполнения процессов.
  • 38. Подсчет количества простых чисел (MPI версия) 38 int count_prime_numbers_par(int a, int b) { int nprimes = 0; // Посчитать '2' как простое int commsize = get_comm_size(); int rank = get_comm_rank(); if (a <= 2) { a = 2; if (rank == 0) nprimes = 1; } for (int i = a + rank; i <= b; i += commsize) { if (i % 2 > 0 && is_prime_number(i)) nprimes++; } int nprimes_global = 0; MPI_Reduce(&nprimes, &nprimes_global, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); return nprimes_global; } Решение проблемы 1 Циклическое распределение итераций (round-robin)
  • 39. Вычислительный узел 1 CPU 1 39 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 Подсчет количества простых чисел (MPI версия) 1 2 43 5 7 87 9 11 1211 13 15 1615 17 18 20192 6 10 14 commsize Решение проблемы 1 Циклическое распределение итераций (round-robin)
  • 41. Дифференцированные обмены 41 Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 Сообщения представляют собой пакеты данных, которые “перемещаются” между процессами. Сообщение имеет следующие параметры: ▪ процесс-отправитель ▪ адрес отправляемых данных ▪ тип отправляемых данных ▪ размер данных ▪ процесс-получатель ▪ адрес данных для получения ▪ тип данных назначения ▪ размер буфера для получения
  • 42. Дифференцированные обмены 42 Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел 1 CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительный узел N CPU 1 Ядро Ядро Общая память (shared memory) CPU 2 Ядро Ядро Вычислительная сеть (MPI) P0 P1 P2 P3 P4 P5 Дифференцированный обмен – простейший вид обменов, обмен вида “точка-точка”. Один процесс передаёт сообщение другому. Виды дифференцированных обменов: ▪ синхронная отправка ▪ асинхронная (буферезированная) отправка ▪ отправка сообщения “по готовности” ▪ неблокируемые обмены
  • 43. 43 Отправка и получение дифференцированных сообщений int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) ▪ buf – адрес для начала отправляемого сообщения с количеством count элементов типа datatype ▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе с сообщением; тэг может быть использован программой для разделения разных типов сообщений. ▪ comm – коммуникатор, в котором находятся процессы. int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) ▪ buf, count, datatype – описывают буфер для получения. ▪ source – ранг процесса в коммуникаторе comm, который отправляет сообщение. ▪ status – результат выполнения операции. ▪ Только сообщение с тэгом tag будет получено.
  • 44. 44 Отправка и получение дифференцированных сообщений int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) ▪ buf – адрес для начала отправляемого сообщения с количеством count элементов типа datatype ▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе с сообщением; тэг может быть использован программой для разделения разных типов сообщений. ▪ comm – коммуникатор, в котором находятся процессы. int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) ▪ buf, count, datatype – описывают буфер для получения. ▪ source – ранг процесса в коммуникаторе comm, который отправляет сообщение (возможно значение source = MPI_ANY_SOURCE для получения сообщения от любого источника). ▪ status – результат выполнения операции (возможно значение status = MPI_STATUS_IGNORE). ▪ Только сообщение с тэгом tag будет получено. (возможно значение tag = MPI_ANY_TAG для получения сообщения с любым тегом).
  • 45. 45 Требования для выполнения дифференцированного обмена ▪ Отправитель должен указать необходимый ранг получателя. ▪ Получатель должен указать корректный ранг отправителя (или получатель использует MPI_ANY_SOURCE). ▪ Коммуникатор должен быть одним и тем же. ▪ Тэги должны совпадать (или получатель использует MPI_ANY_TAG) ▪ Типы данных должны при отправке и получении должны совпадать. ▪ Буфер получателя должен быть достаточно большим.
  • 46. 46 Требования для выполнения дифференцированного обмена ▪ Отправитель должен указать необходимый ранг получателя. ▪ Получатель должен указать корректный ранг отправителя (или получатель использует MPI_ANY_SOURCE). ▪ Коммуникатор должен быть одним и тем же. ▪ Тэги должны совпадать (или получатель использует MPI_ANY_TAG) ▪ Типы данных должны при отправке и получении должны совпадать. ▪ Буфер получателя должен быть достаточно большим. Параметр status получателя содержит актуальную информацию о полученном сообщении: ▪ отправитель: status.MPI_SOURCE ▪ тэг: status.MPI_TAG ▪ количество полученных сообщений: int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count)
  • 47. 47 Дифференцированные обмены: “hello world” int main(int argc, char **argv) { MPI_Init(&argc, &argv); int number; if (get_comm_rank() == 0) { number = 42; MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); } else if (get_comm_rank() == 1) { MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("Process 1 received number %d from process 0n", number); } MPI_Finalize(); return 0; } Process 1 received number 42 from process 0
  • 48. 48 Дифференцированные обмены: “hello world” int main(int argc, char **argv) { MPI_Init(&argc, &argv); int number; if (get_comm_rank() == 0) { number = 42; MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); } else if (get_comm_rank() == 1) { MPI_Status status; MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); int count; MPI_Get_count(&status, MPI_INT, &count); printf("Process 1 received number %d from process 0n" "with status source %d, tag %d, count %dn", number, status.MPI_SOURCE, status.MPI_TAG, count); } MPI_Finalize(); } Process 1 received number 42 from process 0 with status source 0, tag 0, count 1
  • 49. 49 Классификация дифференцированных обменов Дифференцированные обмены Блокируемые Неблокируемые По наличию дополнительного буфера По способу возврата из функции Синхронные Асинхронные (буферезированные)
  • 50. 50 ▪ Выполнение процесса-отправителя приостанавливается, пока сообщение не будет доставлено. ▪ Передача сообщения начинается только тогда, когда принимающий процесс не вызовет функцию получения. ▪ При передаче не используются дополнительные буферы. ▪ Отправитель получает информацию о том, что сообщение было получено. Синхронная отправка сообщений
  • 51. 51 Синхронная отправка сообщений int MPI_Ssend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) Принудительная синхронная отправка сообщения Тип отправки может быть синхронный (определяется библиотекой)
  • 52. 52 ▪ При отправке сообщения используется дополнительный буфер (на стороне отправителя или на стороне получателя). ▪ Выполнение процесса-отправителя может быть продолжено до того, как получатель вызовет функцию получения (т.е. вызов функции получения не обязателен!). ▪ Отправитель “знает” о том, что сообщение было отправлено, но доставка не гарантируется. Асинхронная (буферезированная) отправка
  • 53. 53 Асинхронная (буферезированная) отправка int MPI_Bsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) Тип отправки может быть буферезированный (определяется библиотекой) Принудительная буферезированная отправка сообщения
  • 54. 54 К блокируемым операциям относятся операции ▪ отправки сообщения ▪ получения сообщения Операции блокируются до тех пор, пока другой процесс не выполнит определённую операцию ▪ синхронная отправка блокируется до тех пор, пока сообщение не будет получено ▪ операция получения блокируется до тех пор, пока сообщение не будет отправлено Возврат из функций блокируемых операций происходит только тогда, когда операция завершается. Блокируемые операции MPI_Send MPI_Ssend MPI_Bsend MPI_Recv
  • 55. 55 ▪ Неблокируемые операции возвращают выполнение немедленно и позволяют продолжить выполнение функции, из которой они были вызваны. ▪ Спустя некоторое время можно проверить (test) состояние отправки сообщения или ждать (wait) до завершения неблокируемой операции. Неблокируемые операции MPI_Isend MPI_Irecv MPI_Test MPI_Wait
  • 56. 56 Сообщения содержат некоторое число элементов некоторого типа данных. В MPI типы бывают ▪ Базовые (basic) Типы сообщений MPI Datatype C datatype MPI_CHAR signed char MPI_SHORT signed short int MPI_INT signed int MPI_LONG signed long int MPI_UNSIGNED_CHAR unsigned char MPI_UNSIGNED_SHORT unsigned short int MPI_UNSIGNED unsigned int MPI_UNSIGNED_LONG unsigned long int MPI_FLOAT float MPI_DOUBLE double MPI_LONG_DOUBLE long double
  • 57. 57 Сообщения содержат некоторое число элементов некоторого типа данных. В MPI типы бывают ▪ Базовые (basic) ▪ Производные (derived), которые могут быт получены из базовых и производных типов. Примеры сообщений в MPI Соответствия между базовыми типами MPI и типами С: Типы сообщений
  • 58. 58 Сохранение порядка отправки сообщений Правило для сообщений, имеющих одинаковый коммуникатор, источник, приёмник и тэг: ▪ Сообщения не “обгоняют” друг друга: они доставляются в том же порядке, в котором они были отправлены. ▪ Правило верно также для несинхронных сообщений. P1 P2
  • 59. 59 Пример: пинг-понг ▪ Процесс 0 передаёт сообщение процессу 1 (ping). В качестве сообщения используется счетчик переданных сообщений. ▪ Процесс 0 инкрементирует счетчик сообщений. ▪ После получения, процесс 1 передаёт сообщение обратно процессу 0 (pong). ▪ Процедура повторяется PING_PONG_LIMIT раз. P1 P2 0 1 ping pong pingpong_count 2 3 4 5 6 7
  • 60. 60 Пример: пинг-понг – вариант 1 // Программа работает только для двух процессах if (get_comm_size() != 2) { fprintf(stderr, "World size must be two for %sn", argv[0]); MPI_Abort(MPI_COMM_WORLD, 1); } int pingpong_count = 0; while (pingpong_count < PING_PONG_LIMIT) if (get_comm_rank() == 0) { pingpong_count++; // Увеличиваем счетчик перед отправкой MPI_Send(&pingpong_count, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("0 sent and incremented pingpong_count %dn", pingpong_count); MPI_Recv(&pingpong_count, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("0 received pingpong_count %d from 1n", pingpong_count); } else { MPI_Recv(&pingpong_count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("1 received pingpong_count %d from 0n", pingpong_count); pingpong_count++; // Увеличиваем счетчик перед отправкой MPI_Send(&pingpong_count, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); printf("1 sent and incremented pingpong_count %d to 1n", pingpong_count); }
  • 61. 61 // Программа работает только для двух процессах if (get_comm_size() != 2) { fprintf(stderr, "World size must be two for %sn", argv[0]); MPI_Abort(MPI_COMM_WORLD, 1); } int pingpong_count = 0; int partner_rank = (get_comm_rank() + 1) % 2; while (pingpong_count < PING_PONG_LIMIT) { if (get_comm_rank() == pingpong_count % 2) { // Увеличиваем счетчик перед отправкой pingpong_count++; MPI_Send(&pingpong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD); printf("%d sent and incremented ping_pong_count %d to %dn", get_comm_rank(), pingpong_count, partner_rank); } else { MPI_Recv(&pingpong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("%d received pingpong_count %d from %dn", get_comm_rank(), pingpong_count, partner_rank); } } } Пример: пинг-понг – вариант 2
  • 62. 62 Пример: пинг-понг 0 sent and incremented pingpong_count 1 to 1 1 received pingpong_count 1 from 0 1 sent and incremented pingpong_count 2 to 0 0 received pingpong_count 2 from 1 0 sent and incremented pingpong_count 3 to 1 0 received pingpong_count 4 from 1 0 sent and incremented pingpong_count 5 to 1 0 received pingpong_count 6 from 1 0 sent and incremented pingpong_count 7 to 1 0 received pingpong_count 8 from 1 1 received pingpong_count 3 from 0 1 sent and incremented pingpong_count 4 to 0 1 received pingpong_count 5 from 0 1 sent and incremented pingpong_count 6 to 0 1 received pingpong_count 7 from 0 1 sent and incremented pingpong_count 8 to 0 1 received pingpong_count 9 from 0 0 sent and incremented pingpong_count 9 to 1 0 received pingpong_count 10 from 1 1 sent and incremented pingpong_count 10 to 0
  • 63. 63 double t = MPI_Wtime(); int pingpong_count = 0; int partner_rank = (get_comm_rank() + 1) % 2; while (pingpong_count < PING_PONG_LIMIT) { if (get_comm_rank() == pingpong_count % 2) { // Увеличиваем счетчик перед отправкой pingpong_count++; MPI_Send(&pingpong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD); printf("%d sent and incremented ping_pong_count %d to %dn", get_comm_rank(), pingpong_count, partner_rank); } else { MPI_Recv(&pingpong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("%d received pingpong_count %d from %dn", get_comm_rank(), pingpong_count, partner_rank); } } } t = MPI_Wtime() - t; if (get_comm_rank() == 1) printf("Ping pong execution time: %fn", t); Пример: пинг-понг – вариант 2, с измерением времени
  • 64. 64 ▪ Набор процессов организован в кольцо. ▪ Каждый процесс записывает свой ранг в целой переменной send_buf ▪ Каждый процесс отправляет переменную send_buf своему соседу справа. ▪ Каждый процесс получает значение от своего соседа слева и записывает его в recv_buf, после чего подсчитывает сумму всех полученных значений. ▪ Передача сообщений выполняется до тех пор, пока процесс не получит значение своего ранга, т.е. значение вернётся в точку старта. ▪ Каждый процесс, таким образом, расчитает сумму всех рангов. Пример: передача информации по кольцу 0 3 1 2
  • 65. 65 Пример: передача информации по кольцу rank 0 sendbuf recvbufsum 0 rank 1 sendbuf recvbufsum 0 rank 2 sendbuf recvbufsum 0 Процесс 0 Процесс 1 Процесс 2
  • 66. 66 Пример: передача информации по кольцу rank 0 sendbuf 0 recvbufsum 0 rank 1 sendbuf 1 recvbufsum 0 rank 2 sendbuf 2 recvbufsum 0 Процесс 0 Процесс 1 Процесс 2 1 1 1
  • 67. 67 Пример: передача информации по кольцу rank 0 sendbuf 0 recvbufsum 0 rank 1 sendbuf 1 recvbufsum 0 rank 2 sendbuf 2 recvbufsum 0 Процесс 0 Процесс 1 Процесс 2 1 1 1 2 2 2
  • 68. 68 Пример: передача информации по кольцу rank 0 sendbuf 0 recvbuf 2 sum 0 rank 1 sendbuf 1 recvbuf 0 sum 0 rank 2 sendbuf 2 recvbuf 1 sum 0 Процесс 0 Процесс 1 Процесс 2 1 1 1 2 2 2 3 3 3
  • 69. 69 Пример: передача информации по кольцу rank 0 sendbuf 0 recvbuf 2 sum 2 rank 1 sendbuf 1 recvbuf 0 sum 0 rank 2 sendbuf 2 recvbuf 1 sum 1 Процесс 0 Процесс 1 Процесс 2 1 1 1 2 2 2 3 3 3 4 4 4
  • 70. 70 Пример: передача информации по кольцу rank 0 sendbuf 2 recvbuf 2 sum 2 rank 1 sendbuf 0 recvbuf 0 sum 0 rank 2 sendbuf 1 recvbuf 1 sum 1 Процесс 0 Процесс 1 Процесс 2 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5
  • 71. 71 Пример: передача информации по кольцу rank 0 sendbuf 2 recvbuf 2 sum 2 rank 1 sendbuf 0 recvbuf 0 sum 0 rank 2 sendbuf 1 recvbuf 1 sum 1 Процесс 0 Процесс 1 Процесс 2
  • 72. 72 Пример: передача информации по кольцу rank 0 sendbuf 1 recvbuf 1 sum 3 rank 1 sendbuf 2 recvbuf 2 sum 2 rank 2 sendbuf 0 recvbuf 0 sum 1 Процесс 0 Процесс 1 Процесс 2 2 2 2 3 3 3 4 4 4 5 5 5
  • 73. 73 Пример: передача информации по кольцу rank 0 sendbuf 0 recvbuf 0 sum 3 rank 1 sendbuf 1 recvbuf 1 sum 3 rank 2 sendbuf 2 recvbuf 2 sum 3 Процесс 0 Процесс 1 Процесс 2 2 2 2 3 3 3 4 4 4 5 5 5
  • 74. 74 Пример: передача информации по кольцу int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); int dest = (rank + 1) % size; int source = (rank - 1 + size) % size; int send_buf = rank; int recv_buf = 0; int sum = 0; do { MPI_Send(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD); printf("%d send %d to process %dn", rank, send_buf, dest); MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); sum += recv_buf; send_buf = recv_buf; printf("%d received %d from process %d, sum %dn", rank, recv_buf, source, sum); } while (recv_buf != rank);
  • 75. 75 Пример: передача информации по кольцу Process 2 send 2 to process 0 Process 1 send 1 to process 2 Process 2 received 1 from process 1, sum 1 Process 2 send 1 to process 0 Process 0 send 0 to process 1 Process 1 received 0 from process 0, sum 0 Process 1 send 0 to process 2 Process 2 received 0 from process 1, sum 1 Process 2 send 0 to process 0 Process 0 received 2 from process 2, sum 2 Process 0 send 2 to process 1 Process 0 received 1 from process 2, sum 3 Process 0 send 1 to process 1 Process 0 received 0 from process 2, sum 3 Process 1 received 2 from process 0, sum 2 Process 1 send 2 to process 2 Process 1 received 1 from process 0, sum 3 Process 2 received 2 from process 1, sum 3
  • 76. 76 Синхронная и асинхронная передача сообщений 1 Синхронная передача сообщений Асинхронная передача сообщений2 MPI_Ssend MPI_Bsend MPI_Send MPI_Recv 3 Тип отправки определяется библиотекой
  • 77. 77 Взаимная блокировка (дедлок) int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); int dest = (rank + 1) % size; int source = (rank - 1 + size) % size; int send_buf = rank; int recv_buf = 0; int sum = 0; do { MPI_Send(...); printf("%d send %d to process %dn", rank, send_buf, dest); MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); sum += recv_buf; send_buf = recv_buf; printf("%d received %d from process %d, sum %dn", rank, recv_buf, source, sum); } while (recv_buf != rank); MPI_Ssend MPI_Bsend Дедлок!
  • 78. 78 Взаимная блокировка (дедлок) MPI_Ssend MPI_Recv Процесс 0 MPI_Ssend MPI_Recv Процесс 1 P0 MPI_Ssend в отправителе будет вызвана, но никогда не вернёт выполнение, поскольку MPI_Recv не может быть вызвана в процессе- получателе. P1 P2 P3 P4 При использовании синхронных коммуникационных функций в примере передачи сообщений по кольцу произойдёт взаимная блокировка
  • 79. 79 Передача информации по кольцу: буферезированная отправка int MPI_Bsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) ▪ buf – адрес для начала отправляемого сообщения с количеством count элементов типа datatype; ▪ tag – тэг, дополнительное неотрицательное целое число, передаваемое вместе с сообщением; тэг может быть использован программой для разделения разных типов сообщений; ▪ comm – коммуникатор, в котором находятся процессы; ▪ Пользователь должен подключить буфер достаточного размера с помощью функции MPI_Buffer_attach. int MPI_Buffer_attach(void *buffer, int size) ▪ присоединяет буфер размера size в памяти, начиная с адреса buffer, для отправления сообщений в буферезированном режиме; ▪ только один буфер может быть присоединён к процессу; ▪ отсоединяет присоединённый буфер, возвращает адрес и размер буфера; ▪ операция блокируется до тех пор, пока все сообщения не будут переданы. int MPI_buffer_detach(void *buffer, int *size)
  • 80. 80 Размер буфера при буферизированной отправке ▪ размер буфера должен быть равен суммарному размеру всех передаваемых сообщений плюс MPI_BSEND_OVERHEAD для каждой функции Bsend; ▪ для расчета размера можно использовать MPI_Pack_size: // Пример расчета размера буфера MPI_Pack_size(20, MPI_INT, comm, &s1 ); MPI_Pack_size(40, MPI_FLOAT, comm, &s2 ); size = s1 + s2 + 2 * MPI_BSEND_OVERHEAD; // Размер буфера должен быть не меньше, чем расчитанный size: MPI_Buffer_attach(buffer, size); MPI_Bsend(..., count = 20, datatype = MPI_INT, ...); ... MPI_Bsend(..., count = 40, datatype = MPI_FLOAT, ...); int size; char *buf; MPI_Buffer_attach(malloc(BUFSIZE), BUFSIZE); // ... MPI_Buffer_detach(&buf, &size); // ... MPI_Buffer_attach(buf, size); ▪ Пример присоединения буфера размера BUFSIZE:
  • 81. 81 Пример: передача информации по кольцу // ... int dest = (rank + 1) % size; int source = (rank - 1 + size) % size; int send_buf = rank, recv_buf = 0 int sum = 0; int bufsize = 0; char *buf = NULL; MPI_Pack_size(1, MPI_INT, MPI_COMM_WORLD, &bufsize); bufsize += MPI_BSEND_OVERHEAD; MPI_Buffer_attach(malloc(bufsize), bufsize); do { MPI_Bsend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD); printf("%d send %d to process %dn", rank, send_buf, dest); MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); sum += recv_buf; send_buf = recv_buf; printf("%d received %d from process %d, sum %dn", rank, recv_buf, source, sum); } while (recv_buf != rank);
  • 82. 82 Неблокируемые коммуникации MPI_Isend MPI_Irecv MPI_Test MPI_Wait 1 2 3 ▪ Неблокируемые операции позволяют совместить вычисления и коммуникации. ▪ Особенно актуально в системах, где обмены могут выполняться автономно сетевым контроллером. ▪ Неблокируемая операция завершается ещё до того, как сообщение будет скопировано в буфер отправки (получения). ▪ Операция копирования в буфер отправки (получения) может выполняться параллельно с вычислениями. ▪ Неблокируемая функция отправки, как и обычная, может быть следующих видов: стандартная, буферезированная, синхронная и по готовности.
  • 83. 83 Неблокируемые коммуникации MPI_Isend MPI_Irecv MPI_Test MPI_Wait Выполнение неблокируемых операций разделяются на три фазы: 1. Инициализация неблокируемого обмена данными. Немедленный (Immediate) возврат из функции (MPI_Isend, MPI_Irecv, MPI_Ibcast, MPI_Ireduce, …). 2. Процесс, вызвавший функцию, выполняет какую-то полезную работу (вычисления или другие коммуникации). 3. Ожидание завершения коммуникации (MPI_Wait) или периодическая проверка завершения (MPI_Test). 1 2 3
  • 84. 84 Неблокируемые коммуникации: передача по кольцу Реализация передачи по кольцу на основе неблокируемых обменов: 1. Инициализация неблокируемого обмена данными. Инициализация функции отправки (MPI_Isend) сообщения следующему процессу. 2. Процесс, вызвавший функцию, выполняет какую-то полезную работу. Получение сообщения от правого процесса (MPI_Recv). 3. Ожидание завершения коммуникации (MPI_Wait) или периодическая проверка завершения (MPI_Test). P0 P1 P2 P3 P4
  • 85. 85 Реализация передачи по кольцу на основе неблокируемых обменов: 1. Инициализация неблокируемого обмена данными. Инициализация функции получения (MPI_Irecv) сообщения от предыдущего процесса. 2. Процесс, вызвавший функцию, выполняет какую-то полезную работу. Отправка сообщения следующему процессу (MPI_Send). 3. Ожидание завершения коммуникации (MPI_Wait) или периодическая проверка завершения (MPI_Test). P0 P1 P2 P3 P4 Неблокируемые коммуникации: передача по кольцу
  • 86. 86 Неблокируемые дифференцированные обмены int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) ▪ request – идентификатор, по которому можно обратиться и получить статус операции или дождаться завершения её выполнения; int MPI_Issend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) int MPI_Ibsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request)
  • 87. 87 Неблокируемые дифференцированные обмены int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status) ▪ request – идентификатор, по которому можно обратиться и получить статус операции или дождаться завершения её выполнения; ▪ flag – равен true, если операция завершена; ▪ status – статус завершения операции (м.б. MPI_STATUS_IGNORE). Проверка статуса выполнения неблокируемой операции: int MPI_Wait(MPI_Request *request, MPI_Status *status) Ожидание завершения неблокируемой операции:
  • 88. 88 Пеередача по кольцу на основе неблокируемых обменов int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); int dest = (rank + 1) % size; int source = (rank - 1 + size) % size; int send_buf = rank, recv_buf = 0; int sum = 0; MPI_Request request; do { MPI_Isend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD, &request); printf("%d send %d to process %dn", rank, send_buf, dest); MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); sum += recv_buf; send_buf = recv_buf; printf("%d received %d from process %d, sum %dn", rank, recv_buf, source, sum); MPI_Wait(&request, MPI_STATUS_IGNORE); } while (recv_buf != rank);
  • 89. 89 Пеередача по кольцу на основе неблокируемых обменов int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); int dest = (rank + 1) % size; int source = (rank - 1 + size) % size; int send_buf = rank, recv_buf = 0; int sum = 0; MPI_Request request; do { MPI_Issend(&send_buf, 1, MPI_INT, dest, 0, MPI_COMM_WORLD, &request); printf("%d send %d to process %dn", rank, send_buf, dest); MPI_Recv(&recv_buf, 1, MPI_INT, source, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); sum += recv_buf; send_buf = recv_buf; printf("%d received %d from process %d, sum %dn", rank, recv_buf, source, sum); MPI_Wait(&request, MPI_STATUS_IGNORE); } while (recv_buf != rank);
  • 90. 90 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 MPI_Recv Процесс 1 msgsize = X Размер сообщения определяется в процессе выполнения программы и заранее известен только отправителю.
  • 91. const int MAX_NUMBERS = 100; int numbers[MAX_NUMBERS]; int number_amount; if (world_rank == 0) { // Выбираем случайный размер сообщения srand(time(NULL)); number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS; // Отправляет сообщение случайного размера первому процессу MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("0 sent %d numbers to 1n", number_amount); } else if (world_rank == 1) { MPI_Status status; // Получаем сообщение размера до MAX_NUMBERS от нулевого процесса MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); // Определяем размер полученного сообщения и печатаем сообщение MPI_Get_count(&status, MPI_INT, &number_amount); printf("1 received %d numbers from 0. Message source = %d, " "tag = %dn", number_amount, status.MPI_SOURCE, status.MPI_TAG); } 91 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 Процесс 1 msgsize = X MPI_Recv
  • 92. const int MAX_NUMBERS = 100; int numbers[MAX_NUMBERS]; int number_amount; if (world_rank == 0) { // Выбираем случайный размер сообщения srand(time(NULL)); number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS; // Отправляет сообщение случайного размера первому процессу MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("0 sent %d numbers to 1n", number_amount); } else if (world_rank == 1) { MPI_Status status; // Получаем сообщение размера до MAX_NUMBERS от нулевого процесса MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); // Определяем размер полученного сообщения и печатаем сообщение MPI_Get_count(&status, MPI_INT, &number_amount); printf("1 received %d numbers from 0. Message source = %d, " "tag = %dn", number_amount, status.MPI_SOURCE, status.MPI_TAG); } 92 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 Процесс 1 msgsize = X 0 sent 56 numbers to 1 1 received 56 numbers from 0. Message source = 0, tag = 0 MPI_Recv
  • 93. const int MAX_NUMBERS = 100; int numbers[MAX_NUMBERS]; int number_amount; if (world_rank == 0) { // Выбираем случайный размер сообщения srand(time(NULL)); number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS; // Отправляет сообщение случайного размера первому процессу MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("0 sent %d numbers to 1n", number_amount); } else if (world_rank == 1) { MPI_Status status; // Получаем сообщение размера до MAX_NUMBERS от нулевого процесса MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); // Определяем размер полученного сообщения и печатаем сообщение MPI_Get_count(&status, MPI_INT, &number_amount); printf("1 received %d numbers from 0. Message source = %d, " "tag = %dn", number_amount, status.MPI_SOURCE, status.MPI_TAG); } 93 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 Процесс 1 msgsize = X Перед получением необходимо выделить буфер максимального размера. 0 sent 56 numbers to 1 1 received 56 numbers from 0. Message source = 0, tag = 0 MPI_Recv
  • 94. 94 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 MPI_Probe MPI_Recv Процесс 1 msgsize = X msgsize Получатель сначала определяет размер получаемого сообщения (MPI_Probe), затем выделяет буфер нужного размера и после этого принимает сообщение (MPI_Recv).
  • 95. int numbers[MAX_NUMBERS]; int number_amount; if (world_rank == 0) { const int MAX_NUMBERS = 100; // Выбираем случайный размер сообщения // и отправляем 1 процессу number_acount = ((rand() / (float) RAND_MAX) * MAX_NUMBERS; MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD); printf("0 sent %d numbers to 1n", number_amount); } else if (world_rank == 1) { MPI_Status status; // Проверяем входящее сообщение и получаем его размер MPI_Probe(0, 0, MPI_COMM_WORLD, &status); MPI_Get_count(&status, MPI_INT, &number_amount); // Выделяем буфер необходимого размера и используем его для получения int* number_buf = (int*) malloc(sizeof(int) * number_amount); // Получаем сообщение размера number_buf от нулевого процесса MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); printf("1 received %d numbers from 0", number_amount); free(number_buf); } 95 Пример: обмен сообщениями динамического размера MPI_Send Процесс 0 MPI_Probe MPI_Recv Процесс 1 msgsize = X msgsize
  • 96. 96 Пример: случайное блуждание W a b S ▪ Даны границы области a, b и указатель W. ▪ Указатель W делает S случайных шагов произвольной длины вправо. ▪ Когда блуждатель доходит до конца области, он начинает с начала. typedef struct { // Тип данных указателя int loc; // текущая позиция int steps; // оставшееся число шагов } walker_t; walker.loc = 0; walker.steps = (rand() / (float) RAND_MAX) * max_walk_size while (walker->steps > 0) { if (walker->loc == domain_size) // Если вышли за пределы области walker->loc = 0; // то вернулись в начало области } else { walker->steps--; walker->loc++; } }
  • 97. Схема распараллеливания алгоритма случайного блуждания: 1. Область разбивается на равные части по числу процессов. 2. Указатель выполняет шаги. Как только он достигает границы области, он передаёт состояние указателя соседнему процессу. 3. Процесс, получив состояние указателя, продолжает его перемещение до нужного количества шагов. 97 Случайное блуждание: распараллеливание a b P0 P1 P2 P3 0 4 5 9 10 14 15 20 Процессы b 0 4 5 9 10 14 15 20 Процессы W P0 P1 P2 P3
  • 98. 98 Случайное блуждание: распараллеливание ▪ Каждый процесс определяет границы своей области. ▪ Каждый процесс инициализирует N указателей, которые начинают движение с первого значения их области. ▪ Каждый указатель W имеет два целых значения: p – позиция указателя, s – число шагов, которые остаётся пройти. ▪ Указатели начинают обход по области и передаются другим процессам до тех пор, пока они не закончат перемещение. ▪ Процесс завершается, когда все указатели завершили движение. b 0 4 5 9 10 14 15 20 Процессы W0 (p0 , s0 ) P0 P1 P2 P3 W1 (p1 , s1 ) W2 (p2 , s2 ) W3 (p3 , s3 )
  • 99. 99 Случайное блуждание: декомпозиция области void split_domain(int domain_size, int world_rank, int world_size, int* subdomain_start, int* subdomain_size) { if (world_size > domain_size) { // Размер области должен быть больше числа процессов MPI_Abort(MPI_COMM_WORLD, 1); } *subdomain_start = domain_size / world_size * world_rank; *subdomain_size = domain_size / world_size; if (world_rank == world_size - 1) { // Оставшуюся часть области передать последнему процессу *subdomain_size += domain_size % world_size; } }
  • 100. 100 Случайное блуждание: указатели (walkers) // Тип данных указателя typedef struct { int loc; // текущая позиция int steps; // оставшееся число шагов } walker_t; // Инициализация вектора указателей по заданной подобласти void init_walkers(int nwalkers_per_proc, int max_walk_size, int subdomain_start, int subdomain_size, vector<walker_t>* incoming_walkers) { walker_t walker; for (int i = 0; i < nwalkers_per_proc; i++) { // Начальное положение на подобласти walker.loc = subdomain_start; // Инициализация случайного количества шагов walker.steps = (rand() / (float) RAND_MAX) * max_walk_size; incoming_walkers->push_back(walker); } }
  • 101. 101 Случайное блуждание: функция блуждания // Функция блуждания, которая реализует перемещение указателя // до завершения всех шагов. Если указатель упёрся в границу подобласти, // он добавляется в вектор outgoing_walkers void walk(walker_t* walker, int subdomain_start, int subdomain_size, int domain_size, vector<walker_t>* outgoing_walkers) { while (walker->num_steps_left_in_walk > 0) { if (walker->location == subdomain_start + subdomain_size) { if (walker->location == domain_size) { // Если указатель достиг границы глобальной области, // он переходит на начало walker->location = 0; } // Если указатель достиг границы локальной области, // он добавляется в массив outgoing_walker outgoing_walkers->push_back(*walker); break; } else { walker->num_steps_left_in_walk--; walker->location++; } } }
  • 102. 102 Отправка указателей, достигших границы локальной подобласти // Отправка вектора указателей следующему процессу void send_outgoing_walkers(vector<walker_t>* outgoing_walkers, int world_rank, int world_size) { // Отправить массив указателей следующему процессу. // Последний процесс отправляет сообщение нулевому. MPI_Send((void*)outgoing_walkers->data(), outgoing_walkers->size() * sizeof(Walker), MPI_BYTE, (world_rank + 1) % world_size, 0, MPI_COMM_WORLD); // Очистить массив исходящих указателей outgoing_walkers->clear(); }
  • 103. 103 Получение указателей, достигших границы локальной подобласти void receive_incoming_walkers(vector<walker_t>* incoming_walkers, int world_rank, int world_size) { MPI_Status status; // Получение сообщения неизвестного размера от предыдущего процесса. // Процесс 0 полчает от последнего процесса. int incoming_rank = (world_rank == 0) ? world_size - 1 : world_rank - 1; MPI_Probe(incoming_rank, 0, MPI_COMM_WORLD, &status); // Подготовить буфер для получения: изменить его размер на размер // принимаемого сообщения. int incoming_walkers_size; MPI_Get_count(&status, MPI_BYTE, &incoming_walkers_size); incoming_walkers->resize( incoming_walkers_size / sizeof(Walker)); MPI_Recv((void*)incoming_walkers->data(), incoming_walkers_size, MPI_BYTE, incoming_rank, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); }
  • 104. 104 Случайное блуждание. MPI-версия 1. Инициализация указателей. 2. Выполнение функции walk указателями. 3. Все процессы отправляют вектор outgoing_walkers. 4. Все процессы принимают новые указатели в вектор incoming_walkers. 5. Повторять шаги до тех пор, пока все указатели не закончат перемещение. split_domain(domain_size, world_rank, world_size, &subdomain_start, &subdomain_size); // Проинициализировать указатели в локальной подобласти init_walkers(num_walkers_per_proc, max_walk_size, subdomain_start, subdomain_size, &incoming_walkers); while (!all_walkers_finished) { // Обработать все входящие указатели for (int i = 0; i < incoming_walkers.size(); i++) { walk(&incoming_walkers[i], subdomain_start, subdomain_size, domain_size, &outgoing_walkers); } send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); receive_incoming_walkers(&incoming_walkers, world_rank, world_size); }
  • 105. 2 105 Случайное блуждание. MPI-версия – дедлок! split_domain(domain_size, world_rank, world_size, &subdomain_start, &subdomain_size); // Проинициализировать указатели в локальной подобласти init_walkers(num_walkers_per_proc, max_walk_size, subdomain_start, subdomain_size, &incoming_walkers); while (!all_walkers_finished) { // Обработать все входящие указатели for (int i = 0; i < incoming_walkers.size(); i++) { walk(&incoming_walkers[i], subdomain_start, subdomain_size, domain_size, &outgoing_walkers); } send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); receive_incoming_walkers(&incoming_walkers, world_rank, world_size); } b 0 4 5 9 10 14 15 20 P0 P1 P2 P3 a 1 1 1 1 1 2 22 2
  • 106. 106 Случайное блуждание. Способы решения проблемы дедлока b 0 4 5 9 10 14 15 20 P0 P1 P2 P3 a 1. Использование всегда буферезированной функции отправки MPI_Bsend. 2. Неблокируемые коммуникации MPI_Isend, MPI_Irecv. 3. Изменить порядок отправки / получения сообщений так, чтобы каждому send соответствовал recv. Для этого можно a. для чётных процессов указать порядок сначала send, потом recv b. для нечётных процессов сначала recv, потом send 212 1 212 1
  • 107. 2 107 Случайное блуждание. Решение проблемы дедлока b 0 4 5 9 10 14 15 20 P0 P1 P2 P3 a 12 1 split_domain(...); init_walkers(...); while (!all_walkers_finished) { for (int i = 0; i < incoming_walkers.size(); i++) walk(&incoming_walkers[i], subdomain_start, subdomain_size, domain_size, &outgoing_walkers); if (rank % 2 == 0) send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); receive_incoming_walkers(&incoming_walkers, world_rank, world_size); } else { receive_incoming_walkers(&incoming_walkers, world_rank, world_size); send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); } 1 1 2 2 212 1
  • 108. 108 Случайное блуждание. Определение завершения split_domain(...); init_walkers(...); // Определяем максимальное количество обменов, необходимых для завершения // блужданий всех указателей. int maximum_sends_recvs = max_walk_size / (domain_size / world_size) + 1; for (int m = 0; m < maximum_sends_recvs; m++) { for (int i = 0; i < incoming_walkers.size(); i++) { walk(&incoming_walkers[i], subdomain_start, subdomain_size, domain_size, &outgoing_walkers); } if (rank % 2 == 0) send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); receive_incoming_walkers(&incoming_walkers, world_rank, world_size); } else { receive_incoming_walkers(&incoming_walkers, world_rank, world_size); send_outgoing_walkers(&outgoing_walkers, world_rank, world_size); }
  • 109. 109 Случайное блуждание. Пример выполнения 8 processes domain_size = 100 max_walk_size = 1000 num_walkers_per_proc = 10 Process 0 initiated 10 walkers in subdomain 0 - 11 Process 0 sending 10 outgoing walkers to process 1 Process 3 initiated 10 walkers in subdomain 36 - 47 Process 3 sending 10 outgoing walkers to process 4 Process 4 initiated 10 walkers in subdomain 48 - 59 Process 4 sending 10 outgoing walkers to process 5 ... Process 7 received 10 incoming walkers Process 7 sending 10 outgoing walkers to process 0 Process 0 received 10 incoming walkers Process 0 sending 10 outgoing walkers to process 1 ... Process 6 sending 0 outgoing walkers to process 7 Process 6 received 0 incoming walkers Process 6 done Process 7 received 2 incoming walkers Process 7 sending 2 outgoing walkers to process 0 ... Process 6 sending 0 outgoing walkers to process 7 Process 6 received 0 incoming walkers Process 6 done Process 7 received 2 incoming walkers Process 7 sending 2 outgoing walkers to process 0 Process 7 done