Haskell parBuffer是如何工作的?

Haskell parBuffer是如何工作的?,haskell,parallel-processing,Haskell,Parallel Processing,我在并行地查看parBuffer的代码-3.2.0.4,但是我缺少了一些关于它如何工作的信息。除了最初的火花外,我不知道它如何创造新的火花。 就我所看到的,它使用PARBUBER WHNF中的开头来迫使第一个N被PAR触发,然后通过RET,在相同的条目上再次使用PAR(这不应该丢弃Y而不冒获得火花GC的风险吗?),同时返回相应的结果。然后它直接返回xs,没有任何额外的火花产生,因为rdeepseq只是调用pseq 但是很明显,像这样测试代码 withStrategy (parBuffer 10

我在并行地查看parBuffer的代码-3.2.0.4,但是我缺少了一些关于它如何工作的信息。除了最初的火花外,我不知道它如何创造新的火花。 就我所看到的,它使用PARBUBER WHNF中的开头来迫使第一个N被PAR触发,然后通过RET,在相同的条目上再次使用PAR(这不应该丢弃Y而不冒获得火花GC的风险吗?),同时返回相应的结果。然后它直接返回xs,没有任何额外的火花产生,因为rdeepseq只是调用pseq

但是很明显,像这样测试代码

withStrategy (parBuffer 10 rdeepseq) $ take 100 [ expensive stuff ]
我可以看到ghc RTS信息中的所有100个火花,但其他90个火花是在哪里产生的

下面是我看到的代码:

parBufferWHNF :: Int -> Strategy [a]
parBufferWHNF n0 xs0 = return (ret xs0 (start n0 xs0))
  where -- ret :: [a] -> [a] -> [a]
      ret (x:xs) (y:ys) = y `par` (x : ret xs ys)
      ret xs     _      = xs

    -- start :: Int -> [a] -> [a]
       start 0   ys     = ys
       start !_n []     = []
       start !n  (y:ys) = y `par` start (n-1) ys


-- | Like 'evalBuffer' but evaluates the list elements in parallel when
-- pushing them into the buffer.
parBuffer :: Int -> Strategy a -> Strategy [a]
parBuffer n strat = parBufferWHNF n . map (withStrategy strat)


parBuffer
在概念上类似于循环缓冲区,具有恒定的窗口大小滚动输入并生成输出,在实现管道并行或处理惰性流时非常有用

它的实现在内部取决于如何评估结果——它使 使用懒散和图形共享(这解释了为什么火花不会被丢弃)来产生输出,因为输入被消耗,确保线程的数量被限制在
N
,因此使用了恒定的空间(与参数列表长度呈线性的
parList
相反)

start
功能用于创建初始
N
火花,并将其余输入传递到
ret
未标记。
ret
函数获取两个列表(
xs0
xs0
,但不包含由
start
返回的初始
N
元素),并触发一个元素
每次线程完成时从第二个列表开始(结果中的
x
;这实际上在用户要求结果时发生),直到没有剩余元素

start
实际上会丢弃它并行计算的
n
元素,因此在
ret
中获得
par
ys
与在
start
中获得
par
ed的
ys
并不相同。初始火花没有GCed,因为在结果(
xs
)中仍然有对它们的引用。基本上,这种递归的目的是通过使用
start
,将
n
元素的计算结果与返回的内容进行比较。因此,它计算前n个内容,然后随着列表中的更多内容被消耗掉,会计算更多内容,对吗?它会产生n个火花(可能并行计算)然后,随着结果列表的消耗,会产生更多火花。西蒙·马洛的书的第三章中有一个例子