我在做欧拉计划第21题的家庭作业,我有这样一个理解列表:
amicableNumberSums = [ x+y | x<-[1..10000], y <-[1..10000], (amicable x y)]
这需要很长的时间来执行(可以理解,因为它测试10000^2对数字),看看我的cpu使用情况,它显示只有1个内核正在使用。
由于对列表解析没有副作用,因此不存在同时测试多对数字的危险。有没有办法让Haskell自动做这件事,或者如果没有,怎么能修改我的代码来做这件事?
(编辑)运行打印时出错(amicableNumberSums using
parList):
Couldn't match type `a0 -> Eval a0' with `[Int]'
Expected type: Strategy [Int]
Actual type: Strategy a0 -> Strategy [a0]
In the second argument of `using', namely `parList'
In the first argument of `print', namely
`(amicableNumberSums `using` parList)'
In the expression: print (amicableNumberSums `using` parList)
(编辑)两种建议方法的性能:
Ørjan Johansen's method: 218.79s elapsed parallel (4 cores + 4 hyperthreading)
279.37s elapsed sequential (single core)
bheklilr's method: 247.82s elapsed parallel (4 cores + 4 hyperthreading)
274.10s elapsed sequential (single core)
Original method: 278.69s elapsed
这不是一个大的速度,因为我希望,但我现在有正确的答案,这个问题,直到我学会了一些更多的 haskell 这是足够的。
3条答案
按热度按时间velaa5lx1#
下面是一个简单的例子:
如何将其并行化?如何将其推广到任何列表解析,以确定它是否可以并行化?对于无限列表的任何其他列表解析,为每个元素启动一个新线程意味着启动无限多个线程。如果顺序计算列表实际上要快得多,因为线程开销太大,降低了计算速度,该怎么办?如果只需要列表的前10个元素呢?当需要一小部分元素时,贪婪地计算整个列表不是很好。
相反,GHC选择让程序员决定何时以及如何并行化列表计算,而不是隐式地为您做事情,您可以使用
Control.Parallel.Strategies
模块来选择如何做:或者并行计算列表的块:
请记住,您必须使用
seq
和co.在适当的时候将数据放入NF。Control.Parallel.Strategies
提供的API允许您定义不同的方法来并行计算纯数据结构,完全独立于数据结构本身甚至其他算法。这与大多数其他编程语言形成了鲜明的对比,这些语言迫使您将并行性与其他算法紧密耦合。我强烈推荐阅读Simon Marlow的Parallel and Concurrent Haskell(它在网上是免费的!),它在解释它如何工作和如何使用方面比我做得好得多。rpppsulh2#
@bheklilr的答案处理了并行化策略的一般方法,但是正如我在上面的评论中暗示的,原始列表解析的编写方式迫使所有
amicable
测试在基于parList
的策略到达其元素并开始计算它们之前发生,所以我不认为@bheklilr的代码在这种特定情况下会很好地工作。下面是我的改进建议:你需要重写你的列表解析,这样它就可以把工作划分成定义良好的独立块,例如,通过组合中间列表中的
x
值。现在,您可以在理解和最终
concat
之间放置一个并行评估策略:(我在这里使用
rdeepseq
是因为预连接列表的元素也是列表,我们希望计算它们所有的整数元素。)lhcgjxsq3#
OP报告的结果并不令人满意,我进一步挖掘了一下。事实证明,使用多核不仅需要其他答案中提到的显式并行代码,还需要特定的编译和运行时标志。具体到
ghc
,它们分别是-threaded
和+RTS -N
。有了这些设置,我在使用10个核的情况下获得了5倍的速度提升。完整的可运行程序:
注意,我将
x
的上限减少到1000是为了理智起见,并且我刚刚从here复制了helper函数。结果(在我的苹果M1 Max上)是:
因此,仅仅设置
-O2
就可以将运行时间提高55倍!根据GHC手册,-O2
是最高级别的“方便包”优化和方法:应用每一个不危险的优化,即使这意味着明显更长的编译时间。
现在我们添加并行性。
使用bheklilr答案中的示例代码,我无法逐字检测运行时行为的任何变化,所以我将这些结果省略。
相反,让我们直接使用Ørjan Johansen的方法,将
amicableNumberSums
改为:如果我像以前那样编译和运行,结果不会有有意义的改变,这表明对
concat
的改变没有引入显著的开销,并且默认情况下,执行仍然绑定到单核上。要在多核上运行,ghc
需要一些鼓励:因此,似乎有相当多的开销(几乎两倍的CPU时间),但通过投入10个核心,我们仍然得到了5倍的速度提高。
-threaded
和+RTS -N
标志是紧密联系在一起的--你不能只拥有一个而不拥有另一个。+RTS
标志表示后面的标志是用于“运行时系统”的,-N <x>
指定要使用的“并发线程”的数量。如果未指定<x>
,则运行时将自行选择基于有多少处理器在您的机器”。更多关于这些选项的信息在罚款手册。