Haskell에서 병렬 "any"또는 "all"


9

내가 여러 번 겪어 본 패턴은 테스트를 매핑하고 일부 또는 모든 요소가 통과했는지 확인하여 값 목록을 확인 해야하는 패턴입니다. 전형적인 솔루션은 편리한 내장 기능을 사용하는 것입니다 allany.

문제는 이것들이 연속적으로 평가된다는 것입니다. 많은 경우에는 것 정도 되면 프로세스가 완료되고 병렬로 평가 빠른 어떤 스레드가 "거짓"를 찾아 all나에 대한 "true"로 any. 프로세스 간 통신이 필요하기 때문에 Control.Parallel을 사용하여 단락 동작을 구현할 수 없다고 확신하며 아직 구현하기에 충분한 Control.Concurrent 근처의 곳을 이해하지 못합니다.

수학에서 매우 일반적인 패턴입니다 (예 : Miller-Rabin Primality). 누군가 이미이 문제에 대한 해결책을 찾은 것 같은 느낌이 들지만, "병렬 또는 / 및 / 모든 목록에 대해 Google 검색을하는 명백한 이유로 haskell "은 많은 관련 결과를 반환하지 않습니다.


1
Haskell의 병렬 및 동시 프로그래밍 , 특히 2 장 , 3 장 및 4에서 유용 할 수 있습니다 .
bradrn

2
이것은 unamb라이브러리에서 가능 합니다
luqui

1
@luqui Fascinating; 나는 이것으로 엉망이 될 것이다. 이것과 함께 모든 것을 병행하여 쓰면 답으로 게시 할 것입니다.
Arcuritech

11
병렬화하기 전에 새로운 프로세스를 수행하는 데 걸리는 시간을 테스트 할 수있는 조건 수를 고려하십시오.
chepner

2
@chepner 무슨 소리 야? 우리는 여기서 배쉬에 대해 이야기하고 있지 않습니다! 스레드와 동시성 및 병렬 처리를 수행 할 수 있습니다 ( pthreads하스켈의 C 또는 녹색 스레드). 동시 웹 요청을 처리하기 위해 여러 웹 서버를 시작하지 않고 단일 프로세스에서 여러 스레드를 실행합니다! 병렬 처리에도 동일하게 적용됩니다. CPU 수만큼 스레드를 늘리고 작업을 균등하게 분할하여 CPU 바운드 작업을 처리합니다. 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각각 4 천만 개의 100 개의 PRNG 스트림 출력에서 검색 할 수 없습니다. 컴파일하고 실행하십시오.

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

그리고 약 16 초 동안 4 개의 코어를 뚫고 결국 인쇄 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 % CPU에서 (최소한 잠시 동안) 페깅 된 상태로 유지되므로 불필요한 계산이 계속 실행되고 단락되지 않았 음을 알 수 있습니다.

그러나 대답을 얻은 후 가비지 수집을 강제 실행하면 상황이 변경됩니다.

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

이제 인쇄 직후 CPU가 유휴 상태로 떨어지고 True통계에 따르면 대부분의 계산이 실행 전에 가비지 수집되었음을 알 수 있습니다.

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

실제 프로그램에서는 performGCGC가 당연히 정기적으로 수행 되므로 명시적인 것이 필요하지 않습니다. 답을 찾은 후에도 일부 불필요한 계산이 계속 실행되지만 많은 실제 시나리오에서 불필요한 계산의 비율은 특히 중요하지 않습니다.

특히, 목록이 크고 목록 요소에 대한 개별 테스트가 빠르면 병렬 전략은 실제 성능이 뛰어나고 거래에 쉽게 적용 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.