Финал цикла статей о параллелизме

Очень медленный фильм, очень характерный для стиля Андрея Тарковского, Offret/The Sacrifice (1986), имеет следующее вступительное повествование:

«Знаете, иногда я говорю себе, если каждый божий день, в один и тот же удар часов, совершать одно и то же единичное действие, подобное ритуалу, неизменному, систематическому, каждый день в одно и то же время, мир было бы изменено. Да, что-то изменится, это должно быть».

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

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

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

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

Мы уже рассмотрели ключевые понятия параллелизма в Части I, обсудили некоторые способы предоставления взаимоисключающего доступа к ресурсу в Части II и изучал реализацию параллелизма в языках программирования Ada, Java и C# в Часть III. Опять же, не будем забывать, что все эти статьи в основном основаны на книге Concepts of Programming Languages Robert W. Sebesta.

Сегодня наступает судный день, и Часть IV посвящена реализации параллелизма в функциональных языках программирования. Если у вас возникли трудности с пониманием парадигмы функционального программирования, то вы можете прочитать мой вечно серый эссе о Лиспе.

Параллелизм в Multi-Lisp

Multi-Lisp — это параллельная версия языка программирования Scheme, когда параллелизм вызывается неявно. Для этой цели служит конструкция pcall (макрос). Однако его безопасность находится под вопросом. В результате в центре внимания оказывается конструкция future, которая считается более продуктивной. Как и pcall, он оценивает вызовы функций внутри него в параллельном потоке. Здесь, если родительскому потоку требуется возвращаемое значение от функции, которая не завершила свое выполнение, то родительский поток ожидает ее завершения. Фьючерсы напоминают форкинг в сочетании с ленивой оценкой.

Параллельное машинное обучение

Параллельная версия языка программирования ML. В CML параллелизм поддерживается с помощью потоков и синхронной передачи сообщений. Поток создается с помощью примитива spawn. После создания потока его функциональный параметр начинает выполняться в новом потоке. Это происходит либо с произведенным выводом, либо посредством связи с другими потоками. Завершение потоков, будь то родительский или дочерний, не влияет на выполнение другого.

Для связи потоков друг с другом используются каналы (по аналогии с горутинами и каналами в языке программирования Go). Здесь используются две основные функции — отправить и получить (получить) сообщения. Тип операции отправки ниже предполагается целочисленным, когда recv возвращает значение канала.

let val ch = channel()
send(ch, 10)
recv(ch)

Отправка и получение могут происходить, когда и отправитель, и получатель готовы (в противном случае они ждут). Функции могут принимать каналы в качестве параметров. Так же, как и в языке программирования Ада, синхронная передача сообщений должна иметь возможность решить, какое сообщение выбрать, когда его получают более чем по одному каналу, когда защищенная команда do-od конструкция делает случайный выбор. Событие — это механизм синхронизации в CML.

F#

F# является частью семейства .NET [1], поэтому его сходство с C# при реализации параллелизма будет выглядеть крайне странно.

let createThread() = 
    let th = new Thread(MethodMan)
    th.Start()

Синхронизация не требуется для общих неизменяемых данных, но с изменяемыми данными дело обстоит иначе. В этом случае используются замки. Как отмечено в книге, взаимная переменная, выделенная в куче, имеет тип ref. Его можно изменить с помощью лямбда-выражения, в котором используется оператор :=. Префикс ! получает значение ref.

let s = ref 0
lock(s) (fun () -> s := !s + x)

Узнайте об изменяемых и неизменяемых объектах в Python в статье Analytics Vidhya.

Асинхронно вызываемые потоки используют подпрограммы BeginInvoke и EndInvoke и интерфейс IAsyncResult, как и в C# (см. предыдущую статью о параллелизме для получения дополнительной информации).

Параллелизм в Лиспе

Чтобы узнать о параллелизме в Лиспе, обратитесь к статье эта.

Вот и все о функциональных языках программирования. Все кончено, иди домой. Но если вам интересно просмотреть всю главу о параллелизме, продолжайте читать, потому что именно здесь Кэмерон приходит в ярость.

Параллелизм на уровне операторов в высокопроизводительном Fortran

Мы хотим понять, как программист может сообщить компилятору об оптимизации выполнения программ в многопроцессорной архитектуре. Высокопроизводительный Фортран (HPF) обеспечивает это.

Основные операторы спецификации в HPF определяют количество процессоров, распределение данных по их памяти и выравнивание данных с другими данными в памяти. Префикс !HPF$ вводит эти операторы, что выглядит круто. Приведенный ниже оператор сообщает компилятору, что для компиляции кода можно использовать 3 процессора.

!HPF$ PROCCESSORS prcs (3)

Спецификации DISTRIBUTE и ALIGN используются на машинах, где у каждого процессора своя память (см. первую часть цикла статей).

!HPF$ DISTRIBUTE (kind) ONTO prcs :: identifier_list

Тип может быть BLOCK или CYCLIC. Для получения дополнительной информации о том, что они на самом деле делают, обратитесь к разделу 13.10.1 книги. В списке идентификаторов есть имена переменных массива, которые будут распределены.

Align, с другой стороны, определяет, как связать распределение массива с другим массивом.

!HPF$ ALIGN array1_element WITH array_2 element

Другой оператор — FORALL, который указывает последовательность операторов присваивания, которые должны выполняться одновременно.

FORALL (index = 1:100)
   list_1(index) = list_2(index)
END FORALL

FORALL идеально сочетается с векторными машинами. В C# реализованы два похожих метода, которые ведут себя как FORALL: Parallel.For и Parallel.ForEach. [2]

Примечания

[0] Рыбы любят говорить о полёте.

[1] Как и другие языки семейства .NET, F# также может взаимодействовать с другими членами через библиотеку базовых классов (BCL) .NET Framework. Он восходит к языкам семейства ML, так как является строго типизированным языком. Стандартный ML, Caml, а также Haskell повлияли на дизайн F# вместе с ранее упомянутым OCaml, который F# пытается имитировать.

[2] Утверждения, которые мы здесь обсудили, являются только вводными и не показывают полную мощность HPF. Для получения дополнительной информации см. здесь.

Читайте предыдущие части: