Практика — OpenMP, MPI и профилирование
Практика - паттерны и типичные ошибки параллелизма
Теория из графов и законов становится полезной, когда вы запускаете код на реальном железе. Здесь — минимальный набор паттернов, который покрывает большинство учебных и инженерных задач на одном узле (OpenMP) и кластере (MPI).
OpenMP — параллелизм по данным на одном узле
OpenMP — стандарт для C/C++/Fortran на одной машине с общей памятью. Вы добавляете директивы (#pragma omp ...) — компилятор и runtime создают потоки и делят работу.
Модель fork-join: один главный поток доходит до #pragma omp parallel → создаётся команда потоков → все вместе работают в region → в конце барьер (синхронизация) → снова один поток.
Параллельный цикл
#include <omp.h>
void saxpy(int n, double a, const double* x, double* y) {
#pragma omp parallel for
for (int i = 0; i < n; ++i)
y[i] = a * x[i] + y[i];
}
| Фрагмент | Смысл |
|---|---|
#pragma omp parallel for | Создать потоки и распределить итерации цикла for |
parallel | «Запустить команду потоков» |
for | «Итерации цикла — общая работа команды» |
y[i] = ... | Каждый i обычно выполняется одним потоком; разные i — параллельно |
Компиляция (GCC/Clang): g++ -fopenmp -O3 saxpy.cpp
Что происходит: runtime делит диапазон 0…n-1 на куски. По умолчанию schedule(static) — равные блоки заранее: поток 0 берёт 0…n/p-1, поток 1 — следующий блок и т.д. Подходит, когда все итерации одинаково тяжёлые.
Reduction (суммирование без гонок)
double sum = 0.0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < n; ++i)
sum += a[i];
Runtime создаёт локальные копии sum на поток, в конце — дерево сложения. Это реализация паттерна из временного анализа (parallel partial sums + tree).
Dynamic schedule при неравной нагрузке
#pragma omp parallel for schedule(dynamic, 64)
for (int i = 0; i < n; ++i)
heavy(i); // время зависит от i
Помогает при load imbalance — см. введение.
NUMA — привязка потоков
export OMP_PROC_BIND=close
export OMP_PLACES=cores
На многосокетном сервере без этого потоки «гуляют» по NUMA-узлам и теряют до 40 % скорости — память и NUMA.
MPI — распределённая память
MPI (Message Passing Interface) — библиотека для нескольких процессов, часто на разных узлах кластера. У каждого процесса своя память; общение только сообщениями.
| Термин MPI | Смысл |
|---|---|
| rank | Номер процесса (0, 1, …, size−1) |
| size | Сколько процессов в группе |
| communicator | Группа процессов (MPI_COMM_WORLD — все) |
| tag | Метка сообщения, чтобы отличать типы обмена |
Каждый rank — отдельный процесс со своей памятью. Программа — SPMD (модели): один и тот же main, разный rank.
Hello + rank
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
printf("Hello from rank %d of %d\n", rank, size);
MPI_Finalize();
return 0;
}
Запуск: mpirun -np 4 ./hello
Point-to-point
if (rank == 0) {
int data = 42;
MPI_Send(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (rank == 1) {
int buf;
MPI_Recv(&buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
Collective — broadcast и reduce
double local_sum = partial_sum();
double global_sum;
MPI_Allreduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
Allreduce — оптимизирован под топологию сети; не пишите свой «каждый шлёт каждому» вручную.
Halo exchange (2D сетка)
Типичный паттерн для domain decomposition:
// rank имеет локальный блок; обмен «призрачными» слоями с соседями
MPI_Sendrecv(halo_send_buf, ..., neighbor_left, ...,
halo_recv_buf, ..., neighbor_left, ..., MPI_COMM_WORLD, &status);
Правило: одно крупное сообщение на направление лучше десятков мелких — latency доминирует.
Overlap — неблокирующий обмен
MPI_Request req;
MPI_Isend(buf, count, MPI_DOUBLE, dest, tag, MPI_COMM_WORLD, &req);
compute_locally(); // пока данные уходят
MPI_Wait(&req, MPI_STATUS_IGNORE);
Гибрид MPI + OpenMP
На суперкомпьютере: 1 MPI-процесс на NUMA-узел или сокет, внутри — OpenMP на ядрах:
#pragma omp parallel for
for (int i = my_start; i < my_end; ++i)
process(i);
Так меньше процессов → меньше дублирования данных и меньше MPI-сообщений, но нужно следить, чтобы OpenMP не создал больше потоков, чем физических ядер (OMP_NUM_THREADS).
Пошаговая отладка параллельного кода
| Шаг | Действие |
|---|---|
| 1 | Эталон последовательно на малых данных |
| 2 | OpenMP с 1 потоком — должен совпасть с эталоном |
| 3 | Рост числа потоков / ranks, сравнение результата (ε для float) |
| 4 | Профилируйте — где время (не гадать) |
| 5 | Strong/weak scaling plots — законы |
Инструменты
| Инструмент | Назначение |
|---|---|
perf, VTune | CPU, cache misses, NUMA |
gprof, -pg | Грубый профиль функций |
mpiP, TAU | MPI: объём и время сообщений |
valgrind --tool=helgrind | Гонки в pthreads |
ThreadSanitizer | -fsanitize=thread в GCC/Clang |
Типичная находка: 80 % времени в одном цикле без параллелизма — сначала #pragma omp parallel for туда, а не «параллелить всё подряд».
Чек-лист перед сдачей HPC-задачи
- Результат совпадает с sequential (или документированная ε).
- Измерены
T_1,T_p, speedup, efficiency на нескольких p. - Нет лишних
barrier/MPI_Barrierв hot loop. - Размер сообщений ≥ нескольких KB (или осознанно мелкие).
- NUMA / affinity настроены на сервере.
- В отчёте указаны: f или s, критический путь (если учебная задача), weak vs strong.
Что дальше
- Инженерия алгоритмов — декомпозиция и halo
- Умножение матриц — Cannon, SUMMA
- Fortran OpenMP/MPI — справочник синтаксиса
- Потоки C++ —
std::thread, mutex
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Введение в параллельные вычисления — зачем они нужны, чем отличаются от асинхронности, основные проблемы высокопроизводительных вычислений (HPC). Сети Петри для моделирования параллельных процессов, диаграммы расписания, связь с графом алгоритма. Классификация параллельных архитектур — таксономия Флинна, SIMD и MIMD, векторно-конвейерные системы, степень достижимого параллелизма. Модели памяти в параллельных системах — общая и распределённая память, мультипроцессоры и мультикомпьютеры, кластеры, GRID и метакомпьютинг. Модели параллельных вычислений — PRAM, message passing, SPMD; сети передачи данных между процессорами; диаграммы расписания. Граф алгоритма — построение, свойства, матрица следования, выявление логически несовместимых операторов и параллелизма. Временные характеристики параллельных алгоритмов — информационный граф, ранние и поздние сроки, критический путь, минимальное число процессоров. Оценка производительности параллельных компьютеров — закон Амдала, закон Густафсона-Барсиса, эффективность, масштабируемость, конвейер. Построение параллельных алгоритмов — инженерный подход, классификация параллелизма, этапы разработки, декомпозиция данных, рекомендации. Параллельные алгоритмы умножения матриц — последовательная база, блочная декомпозиция, Cannon, SUMMA, практические рекомендации. Краткие итоги раздела «Параллельные вычисления». Вопросы для самопроверки по разделу «Параллельные вычисления».Параллельные вычислительные процессы — введение
Сети Петри и формальные расписания
Классификация параллельных архитектур
Память, мультипроцессоры, кластеры и GRID
Модели параллельных вычислений и топологии
Граф алгоритма и матрица следования
Временной анализ параллельных алгоритмов
Законы производительности параллельных систем
Инженерия параллельных алгоритмов
Параллельное умножение матриц
Итоги
Чек-лист самопроверки