我正在阅读 Haskell 中的半显式并行性,并有些困惑。
par::a -> b -> b
人们说这种方法允许我们通过并行评估 Haskell 程序的每个子表达式来自动进行并行化。但是这种方法有以下缺点:
1)它创建了太多的工作小项目,无法有效安排。据我了解,如果对 Haskell 程序的每一行都使用 par 函数,它会创建太多线程,根本不实用。那正确吗?
2) 使用这种方法,并行性受到源程序中数据依赖性的限制。如果我理解正确,这意味着每个子表达式都必须是独立的。就像,在 par 函数中,a 和 b 必须是独立的。
3) Haskell 运行时系统不一定创建线程来计算表达式 a 的值。相反,它会创建一个 Spark ,它有可能在与父线程不同的线程上执行。
所以,我的问题是:最后运行时系统会创建一个线程来计算 a 吗?或者如果需要表达式a来计算表达式b,系统会创建一个新线程来计算a?否则,它不会。这是真的?
我是 Haskell 的新手,所以也许我的问题对你们所有人来说仍然是基本的。感谢您的回答。
最佳答案
par
您提到的组合器是 Glasgow parallel Haskell 的一部分(GpH) 实现了半显式并行,但这意味着它不是完全隐式的,因此不提供自动并行化。程序员仍然需要识别被认为值得并行执行的子表达式,以避免您在 1) 中提到的问题。
此外,注释不是规定性的(例如 C 中的 pthread_create 或 Haskell 中的 forkIO)而是建议性的,这意味着运行时系统最终决定是否并行计算子表达式。这提供了额外的灵活性和动态粒度控制的方式。另外,所谓的Evaluation Strategies已被设计为抽象超过 par
和 pseq
并将协调规范与计算分开。例如,一个策略 parListChunk
允许对列表进行分块并强制其为弱头范式(这是需要一些严格的情况)。
2) 并行性受到数据依赖性的限制,因为计算定义了图的缩减方式以及在哪个点需要进行哪些计算。并不是每个子表达式都必须是独立的。例如E1 par E2返回E2的结果,这意味着有用,E1的某些部分需要在E2中使用,因此E2依赖于E1。
3) 由于 GHC 特定的术语,这里的图片有点困惑。有能力(或 Haskell 执行上下文)实现并行图缩减并分别维护一个 Spark 池和一个线程池。通常每个内核有一个 Capability(可以被认为是 OS 线程)。在连续体的另一端有 Spark ,它们基本上是指向图中尚未评估的部分的指针(thunks)。并且有线程(实际上是某种任务或工作单元),因此要并行评估,需要将 Spark 转换为线程(它具有所谓的线程状态对象,该对象包含必要的执行环境并允许 thunk并行评估)。在这些结果到达之前,thunk 可能取决于其他 thunk 和块的结果。这些线程比 OS 线程轻得多,并且被多路复用到可用的 Capablities 上。
因此,总而言之,运行时甚至不必创建轻量级线程来评估子表达式。顺便说一下,随机工作窃取用于负载平衡。
这是一种非常高级的并行方法,可以通过设计避免竞争条件和死锁。同步是通过图缩减隐式调节的。一个不错的立场声明进一步讨论 Why Parallel Functional Programming Matters .有关幕后抽象机器的更多信息,请查看 Stackless Tagless G-Machine并查看 Haskell Execution Model 上的注释在 GHC wiki 上(通常是与源代码一起的最新文档)。
关于multithreading - Haskell 中的半显式并行性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18562112/