好的,所以我正在做ProjectEuler问题#14,我正在摆弄优化以便感受f#out。
在以下代码中:
let evenrule n = n / 2L
let oddrule n = 3L * n + 1L
let applyRule n =
if n % 2L = 0L then evenrule n
else oddrule n
let runRules n =
let rec loop a final =
if a = 1L then final
else loop (applyRule a) (final + 1L)
n, loop (int64 n) 1L
let testlist = seq {for i in 3 .. 2 .. 1000000 do yield i }
let getAns sq = sq |> Seq.head
let seqfil (a,acc) (b,curr) = if acc = curr then (a,acc) else if acc < curr then (b,curr) else (a,acc)
let pmap f l =
seq { for a in l do yield async {return f a} }
|> Seq.map Async.RunSynchronously
let pmap2 f l =
seq { for a in l do yield async {return f a} }
|> Async.Parallel
|> Async.RunSynchronously
let procseq f l = l
|> f runRules
|> Seq.reduce seqfil
|> fst
let timer = System.Diagnostics.Stopwatch()
timer.Start()
let ans1 = testlist |> procseq Seq.map // 837799 00:00:08.6251990
printfn "%A\t%A" ans1 timer.Elapsed
timer.Reset()
timer.Start()
let ans2 = testlist |> procseq pmap
printfn "%A\t%A" ans2 timer.Elapsed // 837799 00:00:12.3010250
timer.Reset()
timer.Start()
let ans3 = testlist |> procseq pmap2
printfn "%A\t%A" ans3 timer.Elapsed // 837799 00:00:58.2413990
timer.Reset()
为什么与直接映射相比,Async.Parallel代码运行速度非常慢?我知道我不应该看到那么大的影响,因为我只是在双核心Mac上。
请注意,我不想帮助解决问题#14,我只是想知道我的并行代码是什么。
答案 0 :(得分:9)
使用Async.Parallel
似乎是正确的。这些数字看起来确实很可疑,但我不会立即看到这里可能存在的问题。
在任何情况下,异步工作流程实际上更适合涉及某些异步操作的计算(例如I / O,通信,等待事件等)。对于CPU密集型任务,最好使用.NET的Parallel Extensions(现在是.NET 4.0的一部分;不幸的是,没有.NET 2.0版本)。
要从F#执行此操作,您需要F# PowerPack和FSharp.PowerPack.Parallel.Seq.dll
程序集,其中包含用于处理序列的高阶函数的并行版本(例如map
: - ))
这些函数返回类型为pseq<'a>
的值(在C#中称为ParallelQuery<T>
),它表示并行运行的延迟计算(这可以在管道中使用多个操作时实现更好的优化)。还有PSeq.reduce
功能,因此您可能也希望在处理中使用此功能(除了PSeq.map
)。
答案 1 :(得分:3)
在我的盒子上,运行非异步的大约需要半秒钟。由于这里有大约50万个工作项,这意味着每个工作项的平均运行时间约为1微秒。
这些项目太小,无法单独进行并行化。执行线程调度和同步的开销将主导实际工作。您需要选择更好的粒度。
(我会处理一些快速代码......)
编辑:
好的,代码原样并不是很容易重做,以便在没有重大改写的情况下更改批量粒度,因此我没有新的代码可以分享,而不会给出太多的分析。但是我能够让它在我的2核盒子上的运行速度几乎快两倍,每个批次生成50,000个元素,并在每个批次上进行“map reduce”,在10个批次上进行“parallel-map reduce”。
另见
http://blogs.msdn.com/pfxteam/archive/2008/08/12/8849984.aspx
尤其是“任务粒度”部分。
答案 2 :(得分:0)
我只是想知道我的并行代码是什么
嘿,您的并行代码没有错。 ; - )
以下是我如何解决问题:
let rec inside (a : _ array) n =
if n <= 1L || a.[int n] > 0s then a.[int n] else
let p =
if n &&& 1L = 0L then inside a (n >>> 1) else
let n = 3L*n + 1L
if n < int64 a.Length then inside a n else outside a n
a.[int n] <- 1s + p
1s + p
and outside (a : _ array) n =
let n = if n &&& 1L = 0L then n >>> 1 else 3L*n + 1L
1s + if n < int64 a.Length then inside a n else outside a n
let euler14 n =
let a = Array.create (n+1) 0s
let a = Array.Parallel.init (n+1) (fun n -> inside a (int64 n))
let i = Array.findIndex (Array.reduce max a |> (=)) a
i, a.[i]
该程序使用具有安全竞争条件的推测并行性,并在我的8核上实现适度的2倍加速。