Основы параллельного программирования: от принципов к реализации 2:49
Параллельное программирование требует четкого разделения между тем, что программа должна вычислять, и тем, как она фактически выполняется на аппаратном обеспечении. Лекция профессора в рамках курса Stanford CS149 охватывает ключевые концепции: работу с ispc, управление задачами и вопросы синхронизации потоков. Основная идея заключается в том, что эффективная масштабируемость программ зависит от способности программиста грамотно декомпозировать задачи и минимизировать последовательные участки кода.
⚙️ Модель программирования ispc 3:03
ispc (Intel Implicit SPMD Program Compiler) позволяет писать код, который выполняется одновременно в нескольких потоках, используя векторные инструкции (SIMD).
- Принципы работы: При вызове функции
ispcсоздается несколько экземпляров программы (gang). Размер этой группы (gang size) определяет, сколько копий программы запускается параллельно. - Индексация: Каждый экземпляр имеет уникальный идентификатор
program index, который позволяет разделять данные и вычисления между ними. - Исполнение: В своей основе
ispcкомпилирует код в последовательность векторных инструкций. Хотя логически программа может выглядеть как набор независимых экземпляров, физически это реализуется через одну нить выполнения, использующую SIMD-регистры процессора.
Важные конструкции:
for each: Специальный конструкт, который абстрагирует работу с экземплярами. Он говорит компилятору: «У меня есть $N$ итераций, распредели их по рабочим потокам наиболее эффективным способом».- Опасности: Использование
for eachбез должного понимания может привести к состоянию «гонки» (race condition) и неопределенным результатам, если итерации цикла не являются полностью независимыми.
🛠 Управление задачами и производительность 32:06
При создании параллельных задач важно избегать чрезмерных накладных расходов.
- Thread Pool (пул потоков): Использование операционной системы для создания потока под каждую микрозадачу крайне неэффективно. Профессор продемонстрировал, что создание пула из фиксированного числа потоков (соответствующего количеству аппаратных контекстов) работает значительно быстрее, чем постоянное создание и уничтожение потоков.
- Закон Амдала: Скорость параллельной программы ограничена её наиболее медленной последовательной частью. По мнению профессора, даже если 10% кода невозможно распараллелить, максимальное ускорение на 64-ядерной системе составит лишь 8x, что делает минимизацию последовательных участков критически важной.
🔄 Синхронизация и декомпозиция: пример итеративного решателя 53:03
Разбор итеративного решателя (fluid solver) наглядно показывает, как декомпозиция влияет на параллелизм.
- Изменение алгоритма: Если исходный алгоритм имеет жесткие зависимости (каждый шаг зависит от предыдущего), программисту следует использовать доменные знания для поиска альтернативного, более дружелюбного к параллелизму метода, например, «шахматного» обновления ячеек (red-black checkerboarding).
- Синхронизация: При работе с разделяемой памятью необходимо использовать примитивы синхронизации:
- Locks (замки): Обеспечивают атомарность операций над общими переменными, предотвращая «гонку».
- Barriers (барьеры): Синхронизируют фазы вычислений. Поток не может продолжить работу, пока все остальные потоки не достигнут точки барьера.
Рекомендация: Чтобы избежать проблем с производительностью из-за конкуренции за общие ресурсы (например, общую переменную diff), рекомендуется использовать локальные копии данных для накопления промежуточных результатов, которые суммируются лишь один раз в конце итерации.