[C++, Параллельное программирование] Часть 2. MPI — Учимся следить за процессами
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В этом цикле статей речь идет о параллельном программировании с использованием MPI.
- Часть 1. MPI - Введение и первая программа.
- Часть 2. MPI - Учимся следить за процессами.
В предыдущей статье мы обсудили как запускать программу, что такое MPI и зачем нужно это параллельное программирование, если можно писать и без него. В этой статье, предпологаем, что читатель ознакомился с материалом, изложенным в предыдущей и приступаем к следующему шагу изучения технологии MPI, а именно управлению процессами. Дабы избежать негодования опытных программистов далее я буду иметь ввиду под "потоками", "процессами" и т.п. часть вычислительной системы на которой запущен конкретный экземпляр программы (Этой частью может быть как конкретный поток, так и любой вычислительный узел системы).Номера процессов и общее число процессовЧтобы выполнять полезные действия при построении параллельной программы необходимо распределять роли между вычислительными узлами, потоками. Для этого нам просто жизненно необходимо знать какой поток обрабатывает конкретный экземпляр запущенной на нем программы, но для начала неплохо было бы узнать сколько их запущено вообще.Для того чтобы узнать на каком потоке запущена программа существует процедур MPI_Comm_size. Она принимает на вход коммуникатор(о нем пойдет речь далее), и адрес памяти куда будет записано целое число, то есть количество потоков обрабатывающих программу.
int MPI_Comm_size(MPI_Comm comm, int* size)
Так что такое коммуникатор и зачем он собственно нужен? Коммуникатор это такой объект, который хранит в себе информацию о запущенных потоках, доступ к которым ему предоставлен. Роль коммуникатора в программе очень важна, так как большая часть работы с процессами связана именно через него, на то он и называется коммуникатором. В MPI существует глобальный коммуникатор который имеет доступ ко всем запущенным потокам, его название MPI_COMM_WORLD. Также мы можем создавать свои, локальные коммуникаторы для выполнения определенных задач на конкретных потоках, и это довольно мило.Что такое коммуникатор разобрались, теперь было бы неплохо узнать на каком из процессов работает конкретный экземпляр программы. Для этого существует процедура MPI_Comm_size. Она принимает на вход аналогичные параметры, только вместо сохранения количества процессов она сохраняет по адресу номер конкретного процесса. Определена она вот так:
int MPI_comm_rank(MPI_Comm comm, int* rank)
То есть мы передаем ей коммуникатор в котором надо узнать номер процесса и собственно адрес куда его нужно записать.Теперь предлагаю соединить эти 2 важные процедуры и посмотреть на их работу на практике, пока еще конечно на бесполезной программе, но понять как работают эти процедуры она позволит вполне.
#include <stdio.h>
#include "mpi.h"
int main(int argc, char **argv)
{
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Finalize();
printf("Process: %d, size: %d\n", rank, size);
return 0;
}
Выход для 5 потоков будет следующим:
Process: 0, size: 5
Process: 1, size: 5
Process: 2, size: 5
Process: 3, size: 5
Process: 4, size: 5
Как видим каждый поток напечатал свой номер процесса и общее число запущенных процессов.Как это можно использовать? Я думаю вы уже догадались, что уже имея только эту информацию можно использовать возможности параллельного выполнения программы. Допустим у нас есть задача где довольно много однотипных независимых вычислений, будь то сложение, вычитание матриц, векторов, возведение в степень большого числа чисел и т.п. То есть те задачи, где вычисления никак не зависят друг от друга.Работа Comm_size, Comm_rank на примереБолее полезным примером такого распараллеливания будет поиск квадратов чисел в заданном диапазоне. Далее идет небольшой пример программы которая это и делает.
#include <stdio.h>
#include "mpi.h"
int main(int argc, char **argv)
{
const int MAX = 20;
int rank, size;
int n, ibeg, iend;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
n = (MAX - 1) / size + 1;
ibeg = rank * n + 1;
iend = (rank + 1) * n;
for(int i = ibeg; i <= ((iend > MAX) ? MAX : iend); i++)
{
printf("Process: %d, %d^2=%d\n", rank, i, i*i);
}
MPI_Finalize();
return 0;
}
Выход для 5 потоков:
Process: 0, 1^2=1
Process: 0, 2^2=4
Process: 0, 3^2=9
Process: 0, 4^2=16
Process: 1, 5^2=25
Process: 1, 6^2=36
Process: 1, 7^2=49
Process: 1, 8^2=64
Process: 2, 9^2=81
Process: 2, 10^2=100
Process: 2, 11^2=121
Process: 2, 12^2=144
Process: 3, 13^2=169
Process: 3, 14^2=196
Process: 3, 15^2=225
Process: 3, 16^2=256
Process: 4, 17^2=289
Process: 4, 18^2=324
Process: 4, 19^2=361
Process: 4, 20^2=400
Дабы не вставлять огромные участки с кодом я взял число MAX=20. Как видим зная номер процесса на котором исполняется конкретный экземпляр программы уже дает ощутимые возможности, но это далеко не все, ведь сложные задачи далеко не всегда так легко делятся на независимые участки с вычислениями.Важное замечание, которое вы, возможно, заметите: процессы совсем не обязательно будут выводить в консоль в правильном порядке свои результаты. MPI гарантирует лишь корректность вывода в плане целостности данных, то есть дается гарантия что строка будет выведена целиком, но порядок не гарантируется.Пологаю, что все кто попробуют запустить такую программу заметят, что выполнение начинается не сразу, а иногда даже из-за этого программа на одном потоке работает горазда быстрее чем параллельная. С виду кажется не совсем логичным, если не знать о том как работает параллельное выполнение, однако на все есть свои веские причины. Когда срабатывает процедура MPI_Init, довольно большое количество ресурсов тратится на то чтобы собственно породить эти параллельные потоки и создать среду для их корректного выполнения и именно поэтому маленькие программы и мелкие, краткосрочные вычисления, мало того что не требуют подобной оптимизации, так еще и выполняются медленнее из-за того что время порождения потоков занимает существенную часть из времени всех вычислений, а иногда даже и больше чем сами вычисления. Именно поэтому стоит несколько раз подумать нужно ли распараллеливать данный участок вообще.Работа со временемКстати касательно времени. Чтобы работать со временем в MPI можно использовать как стандартные функции из библиотеки <time>, так и процедуры параллельной библиотеки.
double MPI_Wtime(void);
double MPI_Wtick(void);
Первая процедура возвращает на вызвавшем ее процессе астрономическое время в секундах, прошедшее с некоторого момента в прошлом. Какой это конкретно момент не совсем имеет значения, но гарантируется, эта точка отсчета не изменится в течение всего времени выполнения программы. Зная эту процедуру можно довольно легко определить время выполнения конкретного участка кода, ведь разность между Wtime в конце и начале программы как раз его и определяет, все стандартно и вполне знакомо.Таймеры разных процессов не всегда могут быть синхронизированы, для того чтобы узнать так это или нет можно вызвать глобальную переменную MPI_WTIME_IS_GLOBAL, она вернет 0 или 1, то есть не синхронизированы или синхронизированы.Вторая процедура как раз возвращает разрешение таймера конкретного процесса в секундах.И на последок покажу как узнать имя физического процессора на котором выполняется программа. Для этого есть процедура MPI_Get_processor_name. Синтаксис и параметры вот такие:
int MPI_Get_Processor_name(char* name, int* len);
На вход она принимает соответственно ссылку на область памяти куда нужно записать имя физического процессора и ссылку куда будет записана длина этой строки.РезюмируемВ данной статье я наглядно показал как можно работать с процессами в MPI, зачем нам узнавать на каком процессе запущена программа и как работать со временем. Имея в багаже эти знания уже можно вполне успешно создавать простые параллельные программы и управлять ими.Для закрепления полученных знаний предлагаю написать вам простую программу, которая узнает является ли число простым для чисел в заданном диапазоне от 1 до N. Это наглядно покажет вам как можно легко и просто распараллелить вычисления с помощью данной технологии и позволит отложить все полученные навыки в голове.Всем приятного времени суток, хабравчане и те кто набрел на эту статью извне.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, C++] Новый поток в C++20: std::jthread (перевод)
- [C++, Алгоритмы, Параллельное программирование] Часть 1. MPI — Введение и первая программа
- [Программирование микроконтроллеров, Электроника для начинающих] На распутье — Ардуино, Cи или Ассемблер?
- [Программирование, C++, Программирование микроконтроллеров, Производство и разработка электроники] Создание аналога посмертного сore dump для микроконтроллера
- [Open source, C++, Отладка, Разработка под Windows] Полезные скрипты для WinDBG: команда !exccandidates
- [Программирование, C++, Распределённые системы, Микросервисы] С чего начать писать микросервис на C++
- [Программирование, Совершенный код, C++, Лайфхаки для гиков] Прочти меня: код, который не выбесит соседа
- [C++] Доступ к элементам std::tuple во время исполнения программы
- [Программирование, C++, Промышленное программирование, Программирование микроконтроллеров] Маленькие хитрости для STM32
- [Open source, Python, Параллельное программирование] Визуализируйте многопоточные программы Python с open source инструментом – VizTracer (перевод)
Теги для поиска: #_c++, #_parallelnoe_programmirovanie (Параллельное программирование), #_parallelnoe_programmirovanie (параллельное программирование), #_parallelnye_vychislenija (параллельные вычисления), #_mpi, #_c++, #_parallelnoe_programmirovanie (
Параллельное программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:06
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В этом цикле статей речь идет о параллельном программировании с использованием MPI.
int MPI_Comm_size(MPI_Comm comm, int* size)
int MPI_comm_rank(MPI_Comm comm, int* rank)
#include <stdio.h>
#include "mpi.h" int main(int argc, char **argv) { int rank, size; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &size); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Finalize(); printf("Process: %d, size: %d\n", rank, size); return 0; } Process: 0, size: 5
Process: 1, size: 5 Process: 2, size: 5 Process: 3, size: 5 Process: 4, size: 5 #include <stdio.h>
#include "mpi.h" int main(int argc, char **argv) { const int MAX = 20; int rank, size; int n, ibeg, iend; MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &size); MPI_Comm_rank(MPI_COMM_WORLD, &rank); n = (MAX - 1) / size + 1; ibeg = rank * n + 1; iend = (rank + 1) * n; for(int i = ibeg; i <= ((iend > MAX) ? MAX : iend); i++) { printf("Process: %d, %d^2=%d\n", rank, i, i*i); } MPI_Finalize(); return 0; } Process: 0, 1^2=1
Process: 0, 2^2=4 Process: 0, 3^2=9 Process: 0, 4^2=16 Process: 1, 5^2=25 Process: 1, 6^2=36 Process: 1, 7^2=49 Process: 1, 8^2=64 Process: 2, 9^2=81 Process: 2, 10^2=100 Process: 2, 11^2=121 Process: 2, 12^2=144 Process: 3, 13^2=169 Process: 3, 14^2=196 Process: 3, 15^2=225 Process: 3, 16^2=256 Process: 4, 17^2=289 Process: 4, 18^2=324 Process: 4, 19^2=361 Process: 4, 20^2=400 double MPI_Wtime(void);
double MPI_Wtick(void); int MPI_Get_Processor_name(char* name, int* len);
=========== Источник: habr.com =========== Похожие новости:
Параллельное программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 17:06
Часовой пояс: UTC + 5