2011-01-04 12 views
6

Często widzę użycie i wyjaśnienie równoległych strategii Haskell związanych z czystymi obliczeniami (na przykład fib). Jednak nieczęsto go widzę w monadycznych konstrukcjach: czy istnieje rozsądna interpretacja wpływu par i powiązanych funkcji po zastosowaniu do ST s lub IO? Czy przy takim użyciu można uzyskać jakiekolwiek przyspieszenie?Używanie równoległych strategii z monadami

+0

Chcesz, aby twoje operacje we/wy były wykonywane w określonej kolejności (np. Najpierw otwórz plik, zamiast go przeczytać, a następnie go zamknij). Co chcesz tam zrównoleglić? – helium

+1

@helum: wydaje się, że w większości przypadków pojawiają się zmienne dane lub podczas korzystania z FFI. –

+0

Zastanawiam się nad tym. Często otwieram kilka dużych plików (100 megs) i rozpakowuję je w równoległych wątkach, a następnie pracuję z nimi po ich otwarciu. Są na tyle duże, że widać dobrą poprawę wydajności, ale wystarczająco małe, że mogę przechowywać je w pamięci. Zastanawiałem się, jak to zrobić w Haskel. –

Odpowiedz

12

Paralelizm w Monadzie IO jest bardziej poprawnie nazywany "Współbieżnością" i jest obsługiwany przez forkIO i znajomych w module Control.Concurrent.

Trudność z równoległością monady ST polega na tym, że ST jest koniecznie jednowątkowa - to jest jej cel. Istnieje leniwy wariant monady ST, Control.Monad.ST.Lazy, który w zasadzie mógłby wspierać równoległą ocenę, ale nie jestem świadomy, że ktoś próbował to zrobić.

Istnieje nowa monada do równoległej oceny o nazwie Eval, którą można znaleźć w najnowszych wersjach parallel package. Polecam używanie monady Eval z rpar i rseq zamiast obecnie par i pseq, ponieważ prowadzi to do bardziej niezawodnego i czytelnego kodu. Na przykład, zwykle fib przykładem może być napisany

fib n = if n < 2 then 1 else 
     runEval $ do 
      x <- rpar (fib (n-1)) 
      y <- rseq (fib (n-2)) 
      return (x+y) 
1

Istnieją pewne sytuacje, w których ma to sens, ale w ogóle nie powinno się robić. Przeanalizować następujące:

doPar = 
    let a = unsafePerformIO $ someIOCalc 1 
     b = unsafePerformIO $ someIOCalc 2 
    in a `par` b `pseq` a+b 

w doPar, obliczenia dla a jest wywołała, po czym główny gwint ocenia b. Ale możliwe jest, że po zakończeniu głównego wątku obliczenia b, zacznie również oceniać a. Teraz masz dwie wątki oceniające a, co oznacza, że ​​niektóre z operacji IO zostaną wykonane dwukrotnie (lub prawdopodobnie więcej). Ale jeśli jeden wątek zakończy ocenianie a, drugi po prostu upuści to, co zostało zrobione do tej pory. Aby było to bezpieczne, potrzebujesz kilku rzeczy:

  1. Jest bezpieczny dla działań IO wykonywanych wiele razy.
  2. Bezpieczne jest wykonywanie tylko niektórych operacji we/wy (np. Nie ma czyszczenia).
  3. Akcje IO są wolne od jakichkolwiek warunków wyścigu. Jeśli jeden wątek mutuje niektóre dane podczas oceniania a, czy inny wątek działa również w sposób rozsądny? Prawdopodobnie nie.
  4. Wszelkie rozmowy zagraniczne są ponownie uczestnik (trzeba to dla współbieżności w ogóle oczywiście)

Jeśli someIOCalc wygląda to

someIOCalc n = do 
    prelaunchMissiles 
    threadDelay n 
    launchMissiles 

to absolutnie nie jest bezpieczne w użyciu to z par i unsafePerformIO.

Czy kiedykolwiek warto? Może. Iskry są tanie, nawet tańsze niż nici, więc teoretycznie powinno to być zwiększenie wydajności. W praktyce być może nie tak bardzo. Roman Leschinsky ma miłe blog post about this.

Osobiście uważam, że o wiele łatwiej jest wnioskować o forkIO.

Powiązane problemy