Spec-Zone.ru › Haskell 9

6.15.1. Параллельный и конкурентный Haskell

6.15.1.1. Конкурентный Haskell

Конкурентный Haskell — так называется расширение GHC для конкурентного выполнения. Оно включено по умолчанию, поэтому специальные флаги не требуются. Статья о конкурентном Haskell по-прежнему является отличным источником информации, как и Работа с непростой компанией.

Для программиста конкурентный Haskell не вводит новых языковых конструкций; вместо этого он представлен просто библиотекой Control.Concurrent. Функции, экспортируемые этой библиотекой, включают:

  • Создание и уничтожение потоков.
  • Приостановку выполнения.
  • Синхронизированные изменяемые переменные, называемые MVars
  • Поддержку привязанных потоков; см. статью Расширение FFI средствами конкурентного выполнения.

6.15.1.2. Параллельный Haskell

GHC поддерживает параллельное выполнение программ на Haskell на симметричных многопроцессорных системах с общей памятью (SMP). По умолчанию GHC выполняет программу на одном процессоре; чтобы она выполнялась параллельно, необходимо скомпоновать программу с -threaded и запустить её с параметром RTS -N ⟨x⟩; см. раздел Использование параллелизма SMP). Среда выполнения будет планировать выполняющиеся потоки Haskell между доступными потоками ОС, выполняя параллельно столько потоков, сколько указано параметром RTS -N ⟨x⟩.

6.15.1.3. Аннотирование чистого кода для параллельного выполнения

Обычные однопоточные программы на Haskell не получат преимуществ только от включения параллелизма SMP: необходимо явно указать компилятору на возможность параллельного выполнения. Один из способов сделать это — создавать потоки с помощью конкурентного Haskell (Конкурентный Haskell), однако самый простой способ извлечь параллелизм из чистого кода — использовать комбинатор par, тесно связанный с seq (и часто применяемый вместе с ним). Оба доступны в библиотеке parallel:

infixr 0 `par`
infixr 1 `pseq`

par  :: a -> b -> b
pseq :: a -> b -> b

Выражение (x `par` y) создаёт искру для вычисления x (до слабой головной нормальной формы) и возвращает y. Искры ставятся в очередь на выполнение в порядке FIFO, но не выполняются немедленно. Если среда выполнения обнаруживает свободный процессор, она может преобразовать искру в настоящий поток и запустить новый поток на свободном процессоре. Так доступный параллелизм распределяется между реальными процессорами.

Например, рассмотрим следующую параллельную версию нашего старого заклятого врага — nfib:

import Control.Parallel

nfib :: Int -> Int
nfib n | n <= 1 = 1
       | otherwise = par n1 (pseq n2 (n1 + n2))
                     where n1 = nfib (n-1)
                           n2 = nfib (n-2)

Для значений n, превышающих 1, мы используем par, чтобы создать искру для вычисления nfib (n-1), а затем используем pseq, чтобы заставить родительский поток вычислить nfib (n-2), прежде чем перейти к сложению этих двух подвыражений. При таком подходе «разделяй и властвуй» мы создаём новый поток только для одной ветви вычисления (оставляя родительскому потоку вычисление другой ветви). Кроме того, необходимо использовать pseq, чтобы гарантировать, что родительский поток вычислит n2 до n1 в выражении (n1 + n2 + 1). Недостаточно просто переставить выражение в (n2 + n1 + 1), поскольку компилятор может не сгенерировать код, вычисляющий слагаемые слева направо.

Обратите внимание, что мы используем pseq, а не seq. Эти два варианта почти эквивалентны, но слегка различаются поведением во время выполнения: seq может вычислять аргументы в любом порядке, тогда как pseq обязан вычислить первый аргумент до второго, поэтому он лучше подходит для управления порядком вычисления вместе с par.

При использовании par общее практическое правило таково: созданное как искра вычисление должно понадобиться позднее, но не слишком скоро. Кроме того, оно не должно быть слишком маленьким, иначе затраты на его параллельный запуск окажутся слишком велики по сравнению с полученным выигрышем в параллелизме. На практике добиться правильного баланса непросто.

По статистике среды выполнения можно получить некоторое представление о том, насколько хорошо работает par; см. раздел Параметры RTS для управления сборщиком мусора.

Более сложные комбинаторы для выражения параллелизма доступны в модуле Control.Parallel.Strategies пакета parallel. Этот модуль расширяет возможности par, позволяя описывать более сложные шаблоны параллельных вычислений, например параллельный map.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.14.1/docs/users_guide/exts/concurrent.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API