2010-04-20 38 views
3

好吧,所以我正在做ProjectEuler問題#14,並且我在擺弄優化以擺脫f#。F#:告訴我什麼我錯過了關於使用Async.Parallel

下面的代碼:

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

爲什麼它並行然後同步呢?你的管道似乎很奇怪。 – 2010-04-20 21:41:50

+3

因爲我不知道如何獲取值? 我的印象是,Async.Parallel提供了一個設置爲並行處理的列表,然後Async.RunSynchronously運行Async.Parallel-ized序列並等待它完成(但它是並行處理的) – 2010-04-20 21:43:53

回答

9

使用Async.Parallel似乎是正確的。這些數字確實看起來很可疑,但我並不立即看到這裏可能存在什麼問題。在任何情況下,異步工作流程都非常適合涉及一些異步操作(如I/O,通信,等待事件等)的計算。對於CPU密集型任務,最好使用.NET的並行擴展(現在是.NET 4.0的一部分;不幸的是,沒有.NET 2.0版本)。

要做到這一點從F#,你需要F# PowerPackFSharp.PowerPack.Parallel.Seq.dll總成,其中包含高階函數並行版本與序列工作(如map :-))

這些函數返回一個值類型爲pseq<'a>(在C#中稱爲ParallelQuery<T>),它們表示並行運行的延遲計算(當您在流水線中使用多個操作時,可以實現更好的優化)。還有PSeq.reduce函數,所以你可能也想在你的處理中使用它(除了PSeq.map)。

+0

詛咒!這限制了並行擴展到我的電腦的實驗。快點,單聲道!讓我們達到4.0! – 2010-04-20 22:13:58

+0

看起來單聲道的人肯定對Parallel Extensions感興趣http://tirania.org/blog/archive/2008/Jul-26-1.html,但我不確定目前的狀態......我寫了一篇我自己前段時間執行'PSeq.map'和'PSeq.filter',但是性能可能不是最好的... – 2010-04-20 22:37:21

+1

有一個版本的並行擴展爲.NET 2.0。它作爲Reactive Extensions的一部分發布。 – forki23 2010-04-21 12:56:05

3

在我的盒子上,運行非異步程序需要大約半秒。由於這裏約有五十萬個工作項目,這意味着每個項目平均運行約1微秒。

這些項目太小而無法單獨進行並行處理。執行線程調度和同步的開銷將主導實際的工作。你需要選擇更好的粒度。

(我將一些簡單的代碼工作... ...)

編輯:

好吧,按原樣碼是不是太容易重新做更改批次粒度沒有一些重大的重寫,所以我沒有新的代碼分享,不會給太多的東西。但是,通過分批批量生成50,000個元素,並在每個批次上執行「map reduce」,並在10個批次中執行了「parallel-map reduce」,我的運行速度幾乎快於兩個核心框的兩倍。

又見

http://blogs.msdn.com/pfxteam/archive/2008/08/12/8849984.aspx

尤其是 「任務粒度」 一節。

+1

這篇文章幫助我更多地打開了我的眼睛。我鼓勵其他人也閱讀它! – 2010-04-22 20:37:50

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 ×加速。

相關問題