Tomas Jansson
Tomas Jansson

Reputation: 23462

Why is this piece of code not faster?

I've made a "copy" of a piece of java code that runs in approx. 400 ms (https://gist.github.com/threecee/cb1c55ad1ce9ac4b1903). My version in F# that uses Parallel.ForEach and I have also tried with PSeq but none of the versions is faster than 7 seconds.

It's not that I must have my piece of code faster than the one I copied but I would really like to know what could have been done to improve the performance in a sample calculations like this in 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)

What the code does is calculating all the unique products x*y where x: 1->8000 and y: 1-8000.

UPDATE: The updated code after using Array.init as jpe suggested in an answer look likes this:

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)

Upvotes: 4

Views: 175

Answers (2)

Helge Rene Urholm
Helge Rene Urholm

Reputation: 1188

I think the theoretical answer for WHY Java is so much faster could be here: http://palladin.github.io/StreamsPresentation/#/10

Using that library in F# should possibly (or at least its "promised" to) be faster.

Its source is available at: https://github.com/nessos/Streams

Or at nuget: https://www.nuget.org/packages/Streams/

Hopefully then you are comparing Java apples and F# apples (ok not apples, but streams ;-)

It wont change your code to much either I think...

Upvotes: 0

jpe
jpe

Reputation: 1021

You need to use the built in array initialization code as your current array initialization takes ages in the example. Thus replace the line

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

with line

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

and you should get performance comparable to the Java code.

Update: As John Palmer suggested, Array.zeroCreate will make initializing an array to zeros even faster. Thus if you just need to initialize the array to zeros and not compute any initial values, then use

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

Update: The reason why it is so fast has previously been explained here. Short summary: it uses the appropriate IL bytecode command newarr for initialization that in turn has been optimized in the runtime of .Net to be very fast. Array.init is faster than straight forward "manual" initialization because it calls zeroCreateUnchecked to initialize the array and then runs the initialization function over the already initialized array.

If you wonder where the code is then here are the links: implementation of Microsoft.FSharp.Collections.Array that in turn calls internal implementation in Microsoft.FSharp.Primitives.Basics.Array.

Upvotes: 5

Related Questions