Паралельно "будь-яке" або "все" в Haskell


9

Шаблон, на який я стикався вже не раз, - це той, де список значень потрібно перевірити, зіставивши якийсь тест над ним і побачивши, чи пройшли якісь або всі елементи. Типове рішення - просто використовувати зручні вбудовані allта any.

Проблема полягає в тому, що вони оцінюються послідовно. У багатьох випадках було б набагато швидше оцінити паралельно з завершенням процесу, коли будь-який потік знайде "Неправдивий" allабо "Істинний" для any. Я майже впевнений, що поведінку короткого замикання неможливо реалізувати за допомогою Control.Parallel, оскільки це вимагає міжпроцесорної комунікації, і я ще не розумію ніде поблизу достатньої кількості Control.Concurrent для цього.

Це досить поширений зразок математики (наприклад, первинність Міллера-Рабіна), тому я відчуваю, що хтось, мабуть, вже знайшов рішення для цього, але з очевидних причин, роблячи пошук в Google за "паралельним або / та / будь-яким / всім у списку" haskell "не дає багато релевантних результатів.


1
Ви можете вважати корисним паралельне та одночасне програмування в Haskell , особливо глави 2 , 3 та 4 .
bradrn

2
Це можливо з unambбібліотекою
luqui

1
@luqui Захоплююче; Я з цим зіпсуюся. Якщо я напишу хорошу паралель всім / будь-якому з цим, я опублікую це як відповідь.
Arcuritech

11
Перш ніж спробувати щось паралельно, подумайте, скільки умов ви можете перевірити за час, необхідний для розгортання нового процесу.
чепнер

2
@chepner про що ти говориш? Ми не говоримо про баш тут! Ми можемо робити паралельність і паралелізм з потоками (будь то pthreadsв С або зеленими нитками в Haskell). Ви не запускаєте кілька веб-серверів для обробки одночасних веб-запитів, натомість ви запускаєте кілька потоків за один процес! Те саме стосується паралелізму. Ви обертаєте стільки потоків, скільки маєте процесори, і розподіляєте свою роботу рівномірно, опікуючись завданнями, пов'язаними з процесором. Спробуйте цю бібліотеку, щоб переконати себе github.com/lehins/haskell-scheduler
lehins

Відповіді:


2

У багатьох реалістичних програмах для цього можна використовувати паралельні стратегії. Це тому, що, хоча не існує явного механізму для скасування непотрібних обчислень, це відбудеться неявно, коли працює сміттєзбірник. В якості конкретного прикладу розглянемо наступну програму:

import Control.Concurrent
import Control.Parallel.Strategies
import Data.Int
import System.Mem

lcgs :: Int32 -> [Int32]
lcgs = iterate lcg
  where lcg x = 1664525 * x + 1013904223

hasWaldo :: Int32 -> Bool
hasWaldo x = waldo `elem` take 40000000 (lcgs x)

waldo :: Int32
waldo = 0

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)

При цьому використовується паралельна стратегія списку для пошуку waldo = 0(яка ніколи не буде знайдена) у випуску 100 потоків PRNG по 40 мільйонів чисел кожен. Складіть і запустіть його:

ghc -threaded -O2 ParallelAny.hs
./ParallelAny +RTS -s -N4

і він прив’язує чотири керна протягом приблизно 16-х років, врешті-решт друкуючи False. Зауважте в статистиці, що всі 100 іскор "перетворені" і так запускаються до завершення:

SPARKS: 100(100 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

Тепер перейдіть waldoдо значення, яке можна знайти рано:

waldo = 531186389   -- lcgs 5 !! 50000

і модифікуйте mainдля збереження потоку живим протягом 10 секунд:

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)
  threadDelay 10000000

Ви помітите, що воно друкує True майже відразу, але 4 ядра залишаються прив’язаними до 100% ЦП (принаймні на деякий час), ілюструючи, що непотрібні обчислення продовжують працювати і не мають короткого замикання, як ви могли побоюватися.

АЛЕ , все зміниться, якщо ви змусите сміття після отримання відповіді:

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)
  performGC
  threadDelay 10000000

Тепер ви побачите, що процесор втрачає роботу в режимі очікування незабаром після друку True, і статистика показує, що більшість обчислень було зібрано сміття перед запуском:

SPARKS: 100(9 converted, 0 overflowed, 0 dud, 91 GC'd, 0 fizzled)

В реалістичних програмах явна performGCне буде потрібна, оскільки ГК регулярно виконуватимуться. Деякі непотрібні обчислення продовжуватимуться працювати після того, як відповідь буде знайдена, але у багатьох реалістичних сценаріях частка непотрібних обчислень не буде особливо важливим фактором.

Зокрема, якщо список великий і кожен окремий тест елемента списку є швидким, паралельні стратегії матимуть відмінні результати в реальному світі та їх легко втілити в угоду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.