# Stanford CS149: как правильно проектировать параллельные системы?

Источник: https://www.youtube.com/watch?v=0-ztm8SKq70
Канал: Stanford Online
Опубликовано: 15.09.2024

---

## Основы параллельного программирования: от принципов к реализации

[[JUMP:02:49]]

Параллельное программирование требует четкого разделения между тем, что программа должна вычислять, и тем, как она фактически выполняется на аппаратном обеспечении. Лекция профессора в рамках курса Stanford CS149 охватывает ключевые концепции: работу с `ispc`, управление задачами и вопросы синхронизации потоков. Основная идея заключается в том, что эффективная масштабируемость программ зависит от способности программиста грамотно декомпозировать задачи и минимизировать последовательные участки кода.

### ⚙️ Модель программирования `ispc`

[[JUMP:03:03]]

`ispc` (Intel Implicit SPMD Program Compiler) позволяет писать код, который выполняется одновременно в нескольких потоках, используя векторные инструкции (SIMD).

*   **Принципы работы:** При вызове функции `ispc` создается несколько экземпляров программы (gang). Размер этой группы (`gang size`) определяет, сколько копий программы запускается параллельно.
*   **Индексация:** Каждый экземпляр имеет уникальный идентификатор `program index`, который позволяет разделять данные и вычисления между ними.
*   **Исполнение:** В своей основе `ispc` компилирует код в последовательность векторных инструкций. Хотя логически программа может выглядеть как набор независимых экземпляров, физически это реализуется через одну нить выполнения, использующую SIMD-регистры процессора.

**Важные конструкции:**

*   `for each`: Специальный конструкт, который абстрагирует работу с экземплярами. Он говорит компилятору: «У меня есть $N$ итераций, распредели их по рабочим потокам наиболее эффективным способом».
*   **Опасности:** Использование `for each` без должного понимания может привести к состоянию «гонки» (race condition) и неопределенным результатам, если итерации цикла не являются полностью независимыми.

### 🛠 Управление задачами и производительность

[[JUMP:32:06]]

При создании параллельных задач важно избегать чрезмерных накладных расходов.

*   **Thread Pool (пул потоков):** Использование операционной системы для создания потока под каждую микрозадачу крайне неэффективно. Профессор продемонстрировал, что создание пула из фиксированного числа потоков (соответствующего количеству аппаратных контекстов) работает значительно быстрее, чем постоянное создание и уничтожение потоков.
*   **Закон Амдала:** Скорость параллельной программы ограничена её наиболее медленной последовательной частью. По мнению профессора, даже если 10% кода невозможно распараллелить, максимальное ускорение на 64-ядерной системе составит лишь 8x, что делает минимизацию последовательных участков критически важной.

### 🔄 Синхронизация и декомпозиция: пример итеративного решателя

[[JUMP:53:03]]

Разбор итеративного решателя (fluid solver) наглядно показывает, как декомпозиция влияет на параллелизм.

*   **Изменение алгоритма:** Если исходный алгоритм имеет жесткие зависимости (каждый шаг зависит от предыдущего), программисту следует использовать доменные знания для поиска альтернативного, более дружелюбного к параллелизму метода, например, «шахматного» обновления ячеек (red-black checkerboarding).
*   **Синхронизация:** При работе с разделяемой памятью необходимо использовать примитивы синхронизации:
    *   **Locks (замки):** Обеспечивают атомарность операций над общими переменными, предотвращая «гонку».
    *   **Barriers (барьеры):** Синхронизируют фазы вычислений. Поток не может продолжить работу, пока все остальные потоки не достигнут точки барьера.

**Рекомендация:** Чтобы избежать проблем с производительностью из-за конкуренции за общие ресурсы (например, общую переменную `diff`), рекомендуется использовать локальные копии данных для накопления промежуточных результатов, которые суммируются лишь один раз в конце итерации.