Перейти к основному содержимому

Память, мультипроцессоры, кластеры и GRID

Разработчику Архитектору Инженеру

Две фундаментальные модели памяти

Любую параллельную систему можно описать через то, как процессоры видят память:

Главный вопрос для новичка: «Могу ли я, как программист, обратиться к переменной X с другого ядра так же, как с текущего?»

ОтветМодельКак обычно программируют
Да, одна куча на всехShared memoryПотоки, OpenMP, std::thread
Нет, у каждого свой RAMDistributed memoryMPI, отдельные процессы, Send/Recv

Общая память (Shared Memory)

Все процессоры обращаются к одному адресному пространству. Процессор P₁ пишет в ячейку X, процессор P₂ читает X — оба видят одни и те же данные (с учётом когерентности кэша).

┌─────────┐ ┌─────────┐
│ CPU 0 │ │ CPU 1 │
└────┬────┘ └────┬────┘
│ общая │
└───────┬───────┘

[ RAM / шина ]

Плюсы: простой обмен через переменные; хорошо ложится на многопоточность.

Минусы: при росте числа процессоров шина памяти становится узким местом; нужна синхронизация (мьютексы, атомики) — см. потоки.

Примеры: односокетный многоядерный PC, сервер с SMP, NUMA-система (память «ближе» к одному сокету).

Распределённая память (Distributed Memory)

У каждого процессора своя локальная память. Узел A не может просто прочитать адрес узла B — только через явный обмен (сообщения, сеть).

Аналогия: у каждого сотрудника свой шкаф с документами. Чтобы коллега увидел ваш файл, вы отправляете копию (сообщение), а не указываете «возьми из моей памяти по адресу 0x7fff…». В MPI каждый rank — отдельный процесс со своим адресным пространством; общие данные — только через MPI_Send, MPI_Recv, MPI_Bcast.

┌──────────┐ сеть ┌──────────┐
│ CPU + RAM│◄──────►│ CPU + RAM│
└──────────┘ └──────────┘

Плюсы: хорошо масштабируется на тысячи узлов; нет единой точки contention за память.

Минусы: программист (или библиотека) явно отправляет и принимает данные; ошибки в протоколе обмена.

Примеры: кластеры HPC, MPI-программы, распределённое обучение на нескольких серверах.

Практическая памятка
«Несколько потоков в одной программе» → почти всегда shared memory. «Несколько процессов на разных машинах» → distributed memory + MPI или RPC.


Мультипроцессор и мультикомпьютер

МультипроцессорМультикомпьютер
ПамятьОбщая (логически)Распределённая
СвязьШина, crossbar, NUMAСеть (Ethernet, InfiniBand)
МасштабОбычно 1–8 сокетов, до сотен ядерОт десятков до миллионов ядер
ПрограммированиеOpenMP, pthreadsMPI, PGAS
Типичный примерWorkstation, серверКластер / суперкомпьютер

NUMA (Non-Uniform Memory Access) — промежуточный случай: память общая, но скорость доступа зависит от того, «свой» это узел или «чужой». Без NUMA-aware размещения потоков и данных производительность падает на 30–50 %.


UMA и NUMA

  • UMA (Uniform Memory Access) — все ядра видят RAM с одинаковой задержкой (типичный односокетный PC).
  • NUMA — несколько узлов памяти, каждый привязан к группе ядер. Локальная память быстрее удалённой в 1.5–3 раза.

Рекомендации для NUMA:

  • Привязать поток к ядру (taskset, OpenMP proc_bind).
  • Выделять данные на том же узле, где они обрабатываются (numactl --membind).
  • Минимизировать false sharing — когда разные ядра пишут в разные переменные, но в одной кэш-линии.

Кластеры вычислений

Кластер — группа связанных узлов (нод), работающих как единая вычислительная система. В HPC это чаще Beowulf-класс: commodity-серверы + быстрая сеть + общее ПО (Linux, Slurm, MPI).

Компоненты:

КомпонентРоль
Узлы вычисленияЗапускают задачи
Head / login nodeВход пользователя, сборка, отправка job
СетьInfiniBand, Omni-Path, иногда 100 Gb Ethernet
ПланировщикSlurm, PBS, LSF — очередь задач
Общее хранилищеNFS, Lustre, GPFS — большие данные

Суперкомпьютер — по сути очень большой tightly-coupled кластер с топовой сетью и охлаждением.

Kubernetes-кластер в DevOps — тоже кластер, но цель другая (оркестрация контейнеров), не линейная speedup одной численной задачи. Для HPC иногда используют K8s + MPI, но классика — bare metal + Slurm.


GRID и метакомпьютинг

GRID (вычислительная сетка) — объединение разнородных ресурсов (университеты, лаборатории, добровольцы) в единую инфраструктуру для крупных задач. Отличие от кластера:

КластерGRID
АдминистрированиеОдин центрФедерация организаций
ОднородностьВысокаяНизкая (разное железо, ОС)
СвязьНизкая latencyЧасто интернет, высокая latency
ПримерСуперкомпьютер в МГУLHC Computing Grid, BOINC

Метакомпьютинг — уровень над отдельными системами: пользователь отправляет задачу «в сеть», а middleware (Globus, Univa Grid Engine) выбирает, где её выполнить, переносит данные и собирает результат. Программист часто не знает физический узел.

Примеры метакомпьютинга «для всех»:

  • SETI@home, Folding@home — добровольные вычисления на домашних ПК.
  • Облачный burst — докупить 500 VM на время расчёта.

Отображение задачи на систему (проблема mapping)

Проблема отображения — как логические процессы алгоритма (P₀…P_n−1) разместить на физических процессорах и минимизировать время:

  • коммуникаций (на distributed);
  • contention за память (на shared);
  • простоя (load imbalance).

Это NP-трудная задача в общем виде; на практике используют эвристики: блоки матрицы → соседние ядра; 1 MPI-процесс на NUMA-узел; static chunk size в OpenMP.

Формальные модели отображения связаны с топологиями сетей и графом алгоритма.


Программные интерфейсы под модель памяти

МодельAPIКогда выбирать
Shared, 1 машинаOpenMP, TBB, std::threadДо ~64 ядер, один узел
Shared + GPUCUDA, OpenMP target, SYCLМассовый data-parallel
DistributedMPIКластер, несколько узлов
ГибридMPI + OpenMPКрупнейшие суперкомпьютеры: MPI между узлами, OpenMP внутри

Справочник по OpenMP/MPI в контексте Fortran: 811.



Когерентность кэша (MESI — обзор)

В shared memory у каждого ядра свой L1/L2. Как все видят одну переменную X?

Протокол MESI (Modified, Exclusive, Shared, Invalid) — строка кэша в одном из состояний:

СостояниеСмысл
MТолько это ядро изменило строку; память устарела
EТолько это ядро держит копию, не менял
SНесколько ядер читают одну строку
IКопия недействительна

Запись другим ядром → invalidation → cache miss и повторная загрузка. Отсюда false sharing: два потока пишут в разные переменные в одной cache line (64 B) → постоянные invalidations.

Практика: padding структур, локальные буферы, reduction вместо atomic++ на каждый элемент.


First-touch на NUMA

numactl --hardware # узлы NUMA
export OMP_PROC_BIND=close

First-touch: страница RAM выделяется на NUMA-узле того ядра, которое первым записало в неё.

#pragma omp parallel for
for (int i = 0; i < n; ++i)
a[i] = 0; // каждый поток «touch» свой диапазон → локальная память

Если инициализировать массив одним потоком, а считать всеми — данные на «чужом» узле, минус 30–50 % скорости.


Сеть кластера — порядок величин

ТехнологияLatency (тип.)BandwidthКомментарий
Ethernet 1 Gb~100 µs~100 MB/sУчебный кластер
100 Gb Ethernet~10 µs~10 GB/sHPC entry
InfiniBand HDR~1 µs~200 GB/sТоповые суперкомпьютеры
NVLink (GPU)sub-µsсотни GB/sВнутри узла

Правило: одно сообщение ≥ нескольких KB (лучше MB), иначе доминирует latency, а не bandwidth — см. MPI-практику.


Гибрид — узел как SMP, кластер как distributed

Современный HPC-узел — 32–128 ядер, shared/NUMA память. Кластер из N таких узлов:

[ Node 0: OpenMP × 64 ] ←──MPI──→ [ Node 1: OpenMP × 64 ] …
  • Внутри узла — shared memory, halo через shared buffer или OpenMP.
  • Между узлами — MPI, только необходимые блоки.

Так минимизируют число MPI-процессов и не дублируют всю память на каждый rank.


Что дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).