Haskell中的半显式并行性

时间:2013-09-01 19:31:32

标签: multithreading parallel-processing haskell graph-reduction

我正在阅读Haskell中的半显式并行性,并且会产生一些混淆。

      par :: a -> b -> b

人们说这种方法允许我们通过并行评估Haskell程序的每个子表达式来自动进行并行化。但这种方法有以下缺点:

1)它创造了太多的小作品,无法有效地安排。据我所知,如果你对Haskell程序的每一行使用par函数,它将创建太多线程,并且它根本不实用。是吗?

2)使用这种方法,并行性受到源程序中数据依赖性的限制。如果我理解正确,这意味着每个子表达式必须是独立的。比如,在par函数中,a和b必须是独立的。

3)Haskell运行时系统不一定会创建一个线程来计算表达式a的值。相反,它会创建一个spark,它有可能在与父线程不同的线程上执行。

所以,我的问题是:最后运行时系统会创建一个线程来计算或不计算?或者,如果需要表达式a来计算表达式b,系统将创建一个新线程来计算?否则,它不会。这是真的吗?

我是Haskell的新手,所以也许我的问题对你们所有人来说都是基本的。谢谢你的回答。

2 个答案:

答案 0 :(得分:8)

你提到的par组合子是实现半显式并行性的Glasgow parallel Haskell(GpH)的一部分,但是这意味着它不是完​​全隐式的,因此不提供自动并行化。程序员仍然需要识别被认为值得并行执行的子表达式,以避免在1)中提到的问题。

此外,注释不是规定性的(例如C中的pthread_create或Haskell中的forkIO),但是建议,这意味着运行时系统最终决定是否并行评估子表达式。这提供了额外的灵活性和动态粒度控制的方法。此外,所谓的Evaluation Strategies旨在抽象parpseq,并将协调规范与计算分开。例如,策略parListChunk允许对列表进行分块并将其强制为弱头正常形式(这是需要一些严格性的情况)。

2)并行性受到数据依赖性的限制,在这种意义上,计算定义了图形减少的方式以及在哪一点需要哪种计算。每个子表达式都必须是独立的,这是不正确的。例如E1 E2 E2返回E2的结果,这意味着有用,E1的某些部分需要在E2中使用,因此E2依赖于E1。

3)由于GHC特定的术语,这里的图片略有混淆。有能力(或Haskell执行上下文)实现并行图减少并维护火花池和线程池。通常每个核心有一个能力(可以被认为是OS线程)。在连续体的另一端有火花,它们基本上是指向尚未评估的图形部分的指针(thunk)。并且存在线程(实际上是某种任务或工作单元),因此要并行评估需要将一个spark变成一个线程(它有一个所谓的线程状态对象,它包含必要的执行环境并允许thunk到并行评估)。在这些结果到来之前,thunk可能依赖于其他thunk和block的结果。这些线程比OS线程更轻量级,并且被多路复用到可用的Capablities上。

因此,总而言之,运行时甚至不会创建一个轻量级线程来评估子表达式。顺便说一句,随机工作窃取用于负载平衡。

这是一种非常高级的并行方法,可以通过设计避免竞争条件和死锁。同步是通过图形缩减隐式调解的。一个不错的立场声明进一步讨论了Why Parallel Functional Programming Matters。有关幕后抽象机器的更多信息,请查看Stackless Tagless G-Machine并查看GHC wiki上Haskell Execution Model上的注释(通常是源代码旁边的最新文档)。

答案 1 :(得分:3)

  1. 是的,你是对的。你不会通过为你想要计算的每个表达式创建一个火花来获得任何东西。你会得到方式,太多的火花。试图管理这就是Data Parallel Haskell的意义所在。 DPH是一种将嵌套计算分解为大小合适的块的方法,然后可以并行计算。请记住,这仍然是一项研究工作,可能还没有为主流消费做好准备。

  2. 再一次,你是对的。如果a取决于b,则必须计算b需要能够开始计算b的多少。

  3. 烨。与某些替代方案相比,线程实际上具有相当高的开销。火花有点像thunks只有它们可以独立于时间计算。

  4. 不,RTS不会创建计算a的线程。您可以决定RTS应该运行多少线程(六个线程为+RTS -N6),并且它们将在程序期间保持活动状态。

    par只会产生火花。火花不是一个线程。火花占据工作池,调度程序执行工作窃取 - 即当线程空闲时,它从池中获取火花并计算它。