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

Параллельные вычислительные процессы — введение

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

Параллельные вычисления - многоядерность и масштаб

Современный процессор — это уже десятки ядер, GPU с тысячами потоков, кластеры в дата-центрах и суперкомпьютеры из тысяч узлов. Задачи вроде моделирования климата, обучения нейросетей или расчёта конструкций физически не уложатся в разумное время на одном ядре.

Параллельные вычислительные процессы — это способ организовать работу так, чтобы несколько вычислительных элементов (ядра, процессоры, узлы) выполняли части одной задачи одновременно и результат собирался в единое решение.

В реальной инженерии параллелизм опирается на несколько ограничений одновременно —

  • часть программы остаётся последовательной;
  • память отстаёт от потребностей всех ядер;
  • синхронизации и обмены между узлами снижают выигрыш;
  • дисбаланс нагрузки оставляет часть вычислителей простаивать.

Именно поэтому в этой вводной статье мы сначала строим прочную рамку — что именно считается параллелизмом, где его пределы и как избежать ложных ожиданий от масштабирования.


Если вы только начинаете

Сначала — program, process, thread

Параллелизм опирается на то, что ОС запускает процессы (экземпляры программ в памяти) и внутри них — потоки.

Статический файл на диске и эта иерархия — в программе, процессе и потоке; планировщик и переключение контекста — в процессах и потоках выполнения.

Представьте 1000 одинаковых счётов — например, умножить каждый элемент огромного массива на 2. Один человек пройдёт все 1000 строк подряд. Десять человек разделят массив на 100 строк каждый и закончат примерно в 10 раз быстрее — если им никто не мешает и нет "общей тетради", куда все лезут одновременно.

Параллельное программирование учит —

  1. Как делить работу (декомпозиция).
  2. Когда делить нельзя (зависимости — шаг B ждёт результат шага A).
  3. Как соединять части (синхронизация, обмен данными).
  4. Почему 16 ядер редко дают ускорение ×16 (накладные расходы, память, последовательные участки).

В этом разделе сначала — картина мира (статьи 1–4), затем формальный анализ (5–6), законы скорости (7), инженерия и псевдокод (8–10), справочные эталоны OpenMP/MPI (11), опционально — СЛАУ методом Гаусса (12).

Читать до изучения языков

Основные идеи раздела выражены словами, формулами и русским алгоритмическим псевдокодом. Фрагменты на C/C++ в статьях 8–11 — мост к будущим главам про языки; каждый такой блок сопровождается разбором "что означает каждая строка".

Базовые алгоритмы и блок-схемы — в базовой информатике и разделе "Алгоритмы".

Ожидания от первой статьи

Эта глава даёт базовую инженерную оптику. Если после чтения вы начинаете автоматически задавать вопросы "где узкое место?" и "какая модель применима?" — цель статьи достигнута.

Параллелизм и асинхронность — разные задачи

Асинхронность — программа продолжает работу, пока ждёт диск или сеть (раздел про асинхронность).

Пока один запрос "в пути", обрабатываются другие.

Параллелизм — несколько вычислений на CPU/GPU одновременно, чтобы ускорить тяжёлую математику.

В одном приложении часто есть и то и другое — веб-сервер асинхронно принимает запросы, а внутри обработки — пул потоков для расчёта.


Что такое параллельный вычислительный процесс

Вычислительный процесс — последовательность шагов (операторов), которые превращают входные данные в результат — прочитать файл → посчитать → записать отчёт.

Параллельный процесс — когда несколько шагов идут в одно и то же время на разных ядрах или машинах, но только там, где это разрешено зависимостями.

Зависимость по данным — правило "сначала A, потом B" —

a := x + y // шаг 1 пишет в a
b := a * 2 // шаг 2 читает a — ждёт шаг 1
c := x - y // шаг 3 использует только x, y — может идти параллельно с шагом 1

Шаг 3 можно запустить одновременно с шагом 1. Шаг 2 обязан ждать шаг 1. Граф таких связей разбирается в статье 5.

Упрощённая картина —

Последовательно — A → B → C → D → E (время = сумма времён шагов)

Параллельно — A → B ─┬→ C ─→ E
└→ D ─┘ (C и D независимы — идут параллельно)

Ключевой вопрос параллельного программирования — какие шаги можно выполнять одновременно, а какие — нет? Ответ даёт анализ зависимостей — об этом подробно в графе алгоритма и временном анализе.


История вычислений — зачем обществу нужен HPC

Гонка производительности — не "мода ради мегагерц". Она повторяет цепочку: объём данных растёт быстрее, чем один человек или одна машина успевают их обработать в срок.

ЭпохаИнструментПотребность
XVII в.Машина Паскаля, арифмометрыУскорить рутинную арифметику (в т.ч. налоговый учёт)
Конец XIX в.Табулятор ХоллеритаПерепись населения — итоги за месяцы, а не за годы
XX в.ЭВМ, суперкомпьютерыНаука, оборона, промышленность, прогнозы

Пример из практики XIX века: перепись США 1880 года (~50 млн человек) обрабатывалась около 7,5 лет. Табуляторы сократили срок подведения итогов на порядки — это тот же класс задач, что сегодня решают кластеры для климатологии или геномики.

В СССР к концу 1960-х арифмометры "Феликс" ещё выпускались сотнями тысяч штук в год, но уже не закрывали задачи уровня БЭСМ-6 и линейки ЕС ЭВМ — единой системы совместимых машин для науки и промышленности. Параллельные и векторные архитектуры 1980–2000-х продолжили ту же линию — масштаб модели опережает одно ядро.

Сегодня те же драйверы:

  • наука и моделирование — климат, ядерная физика, лекарства;
  • национальная безопасность и критическая инфраструктура;
  • конкурентоспособность — кто быстрее считает и обучает модели, тот раньше выводит продукт.

Высокопроизводительные вычисления — ответ на объективный рост объёма задач, а не на краткоживущий тренд.


Высокопроизводительные вычисления (HPC)

HPC (High-Performance Computing) — область, где критичны максимальная скорость и эффективное использование всей доступной вычислительной мощности — суперкомпьютеры, GPU-кластеры, научные расчёты, рендеринг, ML-тренировки.

HPC — инженерная дисциплина с типичными проблемами


1. Ограниченный параллелизм задачи

Не каждый алгоритм можно бесконечно дробить. В любой программе есть последовательные участки (инициализация, финальная редукция, синхронизация). Закон Амдала показывает — даже 5 % последовательного кода ограничивает ускорение на бесконечном числе ядер.

Пример — перед расчётом нужно один раз прочитать конфиг с диска (2 секунды), затем час считать на всех ядрах. Сколько ядер вы ни добавьте, эти 2 секунды останутся — потолок ускорения всей задачи ограничен.


2. Пропускная способность памяти (memory wall)

Процессор считает быстрее, чем память успевает отдавать данные. На больших массивах программа **упирается в байты/сек из RAM или между узлами NUMA. Без локальности данных (работа с "своим" куском массива) параллелизм только увеличит трафик и замедлит расчёт.


3. Стоимость коммуникаций

На распределённой памяти (кластер, несколько серверов) процессоры обмениваются данными по сети. Передача 1 МБ может стоить микросекунд–миллисекунд — на фоне наносекундных операций в регистре это огромная цена. Параллельный алгоритм должен минимизировать обмен и крупными блоками передавать данные.


4. Накладные расходы на синхронизацию

Барьеры, мьютексы, атомарные операции, ожидание "всех потоков" — всё это останавливает часть вычислителей. Частые синхронизации "съедают" выигрыш от параллелизма.


5. Балансировка нагрузки (load imbalance)

Если один поток получил в два раза больше работы, остальные ждут его в конце этапа. Неравномерное разбиение данных — частая причина ускорения 3× на восьми ядрах.


6. Масштабируемость и эффективность

Ускорение (speedup) — во сколько раз быстрее, чем на одном процессоре. Эффективность — какая доля мощности реально используется. На 1000 ядрах эффективность часто падает до нескольких процентов — типичный эффект масштабирования, который требует отдельного анализа.


7. Сложность отладки

Гонки данных, недетерминированный порядок, "плавающие" баги при изменении числа потоков — параллельные программы сложнее проверять и воспроизводить.

Если коротко — в HPC выигрыш покупается дисциплиной. Нужно одновременно контролировать корректность, производительность и масштабируемость, и каждое решение в одном измерении влияет на два других.


Уровни параллелизма

УровеньПримерТипичные инструменты
Внутри ядраSIMD (AVX), конвейер, SMTКомпилятор, intrinsics
Между ядрами одного CPUOpenMP, std::thread, goroutinesShared memory
Между CPU одного сервераNUMA-aware потокиnumactl, affinity
Между узлами кластераMPI, распределённый TensorFlowMessage passing
GPU / ускорителиCUDA, OpenCL, SYCLМассовый data-parallel

Подробнее об архитектурах — в классификации и моделях памяти.


Параллелизм по задачам и по данным

Два главных стиля (разбор в инженерии алгоритмов) —

  • Task parallelism (параллелизм по задачам) — разные функции или этапы pipeline выполняются на разных процессорах (как конвейер на заводе).
  • Data parallelism (параллелизм по данным)одна и та же операция применяется к разным элементам данных (умножить каждый элемент массива на 2 — классика для GPU).

Многие HPC-программы сочетают оба подхода — данные режутся на блоки (data), а этапы "прочитать → посчитать → записать" идут pipeline (task).


Жизненный цикл параллельной разработки

  1. Постановка — что считаем, какой размер данных, какой дедлайн, какое железо.
  2. Анализ зависимостей — граф алгоритма, что можно параллелить.
  3. Выбор модели — shared memory (OpenMP) vs distributed (MPI) vs GPU.
  4. Декомпозиция — разбиение данных/задач с учётом локальности.
  5. Реализация и синхронизация — минимум блокировок.
  6. Оценка — speedup, efficiency, профилирование (узкие места).
  7. Масштабирование — проверка на большем числе ядер/узлов.

Каждый этап в разделе раскрывается отдельными статьями.

Важный практический принцип — цикл почти всегда итеративный. После профилирования вы возвращаетесь к декомпозиции, затем снова измеряете, и так несколько раундов до приемлемого баланса.


Где это применяется

  • Наука и инженерия — CFD, молекулярная динамика, климат (Fortran + MPI на суперкомпьютерах).
  • ML и AI — матричные операции на GPU (нейросети).
  • Обработка данных — Spark, Dask, параллельные SQL-запросы.
  • Игры и графика — многопоточный рендер, job system на нескольких ядрах.
  • Криптография и рендер — embarrassingly parallel задачи с минимумом связей.

Даже обычный backend иногда выигрывает от параллелизма (пул воркеров, параллельные тесты), но глубокая теория нужна там, где стоимость ошибки в производительности высока — HPC, real-time симуляции, big data на кластере.


Типичные ловушки мышления в начале

  1. Линейный рост от числа ядер — ускорение всегда ограничено последовательной частью и коммуникациями.
  2. Фокус только на CPU — лимит часто задают память или сеть.
  3. Оценка только по speedup — нужны также efficiency и стоимость владения.
  4. Разовая оптимизация — параллельная настройка итеративна и зависит от размера задачи.

Если вы узнаёте себя хотя бы в одном пункте — эта статья поможет перейти к более точной инженерной модели до практической реализации.


Ограничения и метрики — куда смотреть дальше

Законы Амдала и Густафсона, strong/weak scaling, модель Roofline и интерактивные демо ускорения — в главе 7. Там же разбор, почему маленькая задача на многих ядрах почти не ускоряется.

Выбор OpenMP, MPI или GPU — в практике.

Термины раздела (Флинн, NUMA, halo, rank…) — в итогах и терминологии.


Что дальше

ТемаСтатья
Классификация машин (Флинн, SIMD, конвейер)2. Архитектуры
Shared / distributed memory, кластеры, GRID3. Память и системы
Законы Амдала и Густафсона7. Производительность
Граф и матрица следования5. Граф алгоритма
Параллельное решение СЛАУ12. Метод Гаусса