2014-12-06 62 views
4

我已經制作了一個運行在大約20%的Java代碼片的「副本」。 400毫秒(https://gist.github.com/threecee/cb1c55ad1ce9ac4b1903)。我的F#版本使用Parallel.ForEach,我也嘗試過使用PSeq,但沒有一個版本比7秒更快。爲什麼這段代碼不是更快?

這並不是說我必須比我複製的代碼更快,但我真的很想知道在F#中這樣的示例計算中如何提高性能。

open System.Threading.Tasks 
#time 
let calculateProducts n = 
    let bits = [| for i in 1 .. ((n+1)*(n+1)) -> 0 |] 
    let inner i = 
     [|i..n|] |> Array.map (fun j -> bits.[j*i] <- 1) |> ignore 
    Parallel.ForEach([|1 .. n|], (fun i -> inner i)) |> ignore 
    bits |> Array.sum 
printfn "%i" (calculateProducts 8000) 

代碼的作用是計算所有獨特的產品x*y where x: 1->8000 and y: 1-8000

UPDATE: 使用Array.init作爲JPE在回答中表情說明後,更新的代碼很喜歡這樣的:

open System.Threading.Tasks 
#time 
let calculateProducts n = 
    let bits = Array.init ((n+1)*(n+1)) (fun _ -> 0)   
    let inner i = 
     let arr = Array.init (n-i+1) (fun x -> x+i) 
     Parallel.ForEach(arr, (fun j -> bits.[j*i] <- 1)) |> ignore 
    let arr = Array.init n (fun x -> (x+1)) 
    Parallel.ForEach(arr, (fun i -> inner i)) |> ignore 
    bits |> Array.sum 
printfn "%i" (calculateProducts 8000) 
+2

只要使用'Parallel.ForEach'不會自動暗示更好的性能......由於線程/任務初始化會導致性能降低,這可能比循環內的實際操作花費的時間更長。 – Matze 2014-12-06 19:00:14

+0

@Matze,你有什麼建議來改善它?我認爲應該有可能接近鏈接到java代碼的性能。 – 2014-12-06 19:03:16

回答

5

您需要使用內置的數組初始化代碼作爲當前的數組初始化需要年齡在這個例子。因此,與線

let bits = Array.init ((n+1)*(n+1)) (fun _ -> 0) 

更換線

let bits = [| for i in 1 .. ((n+1)*(n+1)) -> 0 |] 

,你應該得到的性能相媲美的Java代碼。

更新:正如約翰帕爾默建議,Array.zeroCreate將使數組初始化爲零更快。因此,如果你只需要初始化數組爲零,不計算任何初始值,然後用

let bits = Array.zeroCreate ((n+1)*(n+1)) 

更新:它之所以這麼快在以前是解釋here。簡短摘要:它使用合適的IL字節碼命令newarr進行初始化,而後者又在.Net的運行時間中被優化爲非常快速。 Array.init比直接「手動」初始化更快,因爲它調用zeroCreateUnchecked來初始化數組,然後在已經初始化的數組上運行初始化函數。

如果您想知道代碼在哪裏,那麼這裏是鏈接:implementation of Microsoft.FSharp.Collections.Array,然後調用internal implementation in Microsoft.FSharp.Primitives.Basics.Array

+0

謝謝先生。不知道Array.init速度如此之快。你知道它快嗎? – 2014-12-06 19:53:06

+6

使用'Array.zeroCreate'可以更快地完成任務 – 2014-12-06 21:33:40