我有一个返回 IO 操作的函数,
f :: Int -> IO Int
我想针对参数的多个值并行计算此函数。我的简单实现如下:
import Control.Parallel.Strategies
vals = [1..10]
main = do
results <- mapM f vals
let results' = results `using` parList rseq
mapM_ print results'
我的理由是第一个 mapM
绑定(bind) IO [Int]
类型的东西至results
, results'
对包含的列表应用并行策略,并且 mapM_
最后通过打印来请求实际值 - 但要打印的内容已经并行触发,因此程序应该并行化。
在很高兴它确实使用了我所有的 CPU 后,我注意到该程序在使用 +RTS -N8
运行时效率较低(如挂钟时间)与没有任何 RTS 标志相比。我能想到的唯一解释是第一个 mapM
必须对所有 IO 操作进行排序(即执行),但这不会导致无效,而是使 N8
执行与非并行执行一样有效,因为所有工作都是由主线程完成的。使用 +RTS -N8 -s
运行程序产量SPARKS: 36 (11 converted, 0 overflowed, 0 dud, 21 GC'd, 4 fizzled)
,这肯定不是最佳的,但不幸的是我无法理解它。
我想我已经在 Haskell 并行化或 IO monad 的内部找到了初学者的垫脚石之一。我做错了什么?
背景信息:f n
是返回欧拉计划问题 n 的解的函数。由于其中很多都有数据要读取,所以我将结果放入 IO monad 中。其外观示例如下
-- Problem 13: Work out the first ten digits of the sum of one-hundred 50-digit numbers.
euler 13 = fmap (first10 . sum) numbers
where
numbers = fmap (map read . explode '\n') $ readFile "problem_13"
first10 n
| n < 10^10 = n -- 10^10 is the first number with 11 digits
| otherwise = first10 $ n `div` 10
完整文件可以在 here 找到(有点长,不过前面几个“euler X”函数应该足够有代表性),我做并行的主文件是this one .
最佳答案
策略用于并行执行纯计算。如果您的 f
确实必须返回 IO
值,那么请考虑使用 async
包。它提供了有用的组合器来同时运行 IO
操作。
对于您的用例,mapConcurrently
看起来很有用:
import Control.Concurrent.Async
vals = [1..10]
main = do
results <- mapConcurrently f vals
mapM_ print results
(不过我还没有测试过,因为我不知道你的 f
到底是什么。)
关于haskell - 并行排序 IO 操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13109946/