.net - Why am I getting poor parallel performance in F# -


i'm trying learn f# , first attempt @ parallel programming. i'm working on puzzle find longest path through grid. pathfinding solution seems straightforward recursive algorithm. map/reduce find paths , return longest.

i have serial implementation , 3 different parallel implementations of map/reduce portion. on smaller grids, see marginal improvement in speed parallel implementations. on larger grid, parallel implementations slower! doing wrong?

the 3 parallel implementations are:

here typical timings 4 implementations using different size input grids:

4x4 grid getlongestpath               19.845400 getlongestpathparallelarray  18.626200 getlongestpathparallelfor     7.084200 getlongestpathpseq          163.271000  5x5 grid getlongestpath              818.967500 getlongestpathparallelarray 629.563000 getlongestpathparallelfor   725.072500 getlongestpathpseq          772.961300  6x6 grid getlongestpath              3941.354000 getlongestpathparallelarray 3609.441800 getlongestpathparallelfor   3509.890500 getlongestpathpseq          3295.218600  7x7 grid getlongestpath              24466.655300 getlongestpathparallelarray 32098.823200 getlongestpathparallelfor   35274.629500 getlongestpathpseq          24980.553600 

here's code:

module pathfinder open system open system.threading.tasks open microsoft.fsharp.collections  let listcontains item list = list.exists (fun x -> x = item) list let longestlist (x:int list) (y:int list) = if x.length >= y.length x else y  let getneighborsnotalreadyinpath (neighbormap: map<int, int list>) path =     neighbormap.[list.head path]     |> list.filter (fun item -> not (listcontains item path))  let rec getlongestpathfromallneighbors neighbormap currentpath longestpath =     let neighbors = getneighborsnotalreadyinpath neighbormap currentpath     if neighbors = []         longestlist currentpath longestpath     else         neighbors         |> list.map (fun neighbor -> getlongestpathfromallneighbors neighbormap (neighbor::currentpath) longestpath)         |> list.reduce longestlist  let getlongestpathfromposition neighbormap =     getlongestpathfromallneighbors neighbormap [i] []  let getlongestpath (neighbormap: map<int, int list>) =     [| 0..neighbormap.count-1 |]     |> array.map (fun -> getlongestpathfromposition neighbormap i)     |> array.reduce longestlist  let getlongestpathparallelarray (neighbormap: map<int, int list>) =     [| 0..neighbormap.count-1 |]     |> array.parallel.map (fun -> getlongestpathfromposition neighbormap i)     |> array.reduce longestlist  let getlongestpathparallelfor (neighbormap: map<int, int list>) =     let inline parallelmap (f: 't -> 'u) (array : 't[]) : 'u[]=         let inputlength = array.length         let result = array.zerocreate inputlength         parallel.for(0, inputlength, fun ->             result.[i] <- f array.[i]) |> ignore         result      [| 0..neighbormap.count-1 |]     |> parallelmap (fun -> getlongestpathfromposition neighbormap i)     |> array.reduce longestlist  let getlongestpathpseq (neighbormap: map<int, int list>) =     [| 0..neighbormap.count-1 |]     |> pseq.map (fun -> getlongestpathfromposition neighbormap i)     |> pseq.reduce longestlist 

here code build map input grid:

module gobstoppers open system  type gobstoppercollection = { items: string[]; width: int; neighbormap: map<int, int list> } type gobstopper = { position: int; color: string; shape: string; }  let creategobstopperfromstring (text:string) =     { position = i; color = text.[0].tostring(); shape = text.[1].tostring() }  let creategobstopper (itemarray: string[]) =     creategobstopperfromstring itemarray.[i]  let findneighbors (itemarray: string[]) rowwidth =     let onleft = (i % rowwidth = 0)     let onright = (i % rowwidth = rowwidth - 1)     let ontop = (i < rowwidth)     let onbottom = (i >= itemarray.length - rowwidth)      [(if ontop || onleft -1 else - rowwidth - 1);      (if ontop -1 else - rowwidth);      (if ontop || onright -1 else - rowwidth + 1);      (if onleft -1 else - 1);      (if onright -1 else + 1);      (if onbottom || onleft -1 else + rowwidth - 1);      (if onbottom -1 else + rowwidth);      (if onbottom || onright -1 else + rowwidth + 1)]     |> list.filter (fun x -> x > -1)  let findcompatibleneighbors itemarray rowwidth =     let arecompatible (a:gobstopper) (b:string) = a.color = b.[0].tostring() || a.shape = b.[1].tostring()     findneighbors itemarray rowwidth     |> list.map (fun x -> creategobstopper itemarray x)     |> list.filter (fun x -> arecompatible x itemarray.[i])     |> list.map (fun x -> x.position)  let load (text:string) =     let itemarray =         text.split('|')         |> array.map (fun x -> x.trim())         |> array.filter (fun x -> x <> "")     let rowwidth = int (sqrt (float itemarray.length))     let neighbormap =          itemarray         |> array.mapi (fun x -> i, findcompatibleneighbors itemarray rowwidth i)         |> map.ofarray      { items = itemarray;       width = rowwidth;       neighbormap = neighbormap } 

here's test input:

module testdata  let testgrid3 = "|yr|rr|rs|                  |yr|gb|rp|                  |bs|gr|yb|"  let testgrid4 = "|yr|rr|rs|gp|                  |yr|gb|rp|pp|                  |bs|gr|yb|bs|                  |br|rs|yb|bb|"  let testgrid5 = "|yr|rr|rs|gp|rb|                  |yr|gb|rp|pp|gr|                  |bs|gr|yb|bs|bp|                  |br|rs|yb|bb|bc|                  |gs|yr|yr|rp|br|"  let testgrid6 = "|yr|rr|rs|gp|rb|bc|                  |yr|gb|rp|pp|gr|pb|                  |bs|gr|yb|bs|bp|ps|                  |br|rs|yb|bb|bc|rs|                  |gs|yr|yr|rp|br|rb|                  |pp|gr|ps|pb|pr|ps|"  let testgrid7 = "|yr|rr|rs|gp|rb|bc|rb|                  |yr|gb|rp|pp|gr|pb|rs|                  |bs|gr|yb|bs|bp|ps|pp|                  |br|rs|yb|bb|bc|rs|pb|                  |gs|yr|yr|rp|br|rb|br|                  |pp|gr|ps|pb|pr|ps|bp|                  |gc|rb|gs|pp|bc|gb|rp|"  let testgrid8 = "|yr|rr|rs|gp|rb|bc|rb|bp|                  |yr|gb|rp|pp|gr|pb|rs|rp|                  |bs|gr|yb|bs|bp|ps|pp|gb|                  |br|rs|yb|bb|bc|rs|pb|pb|                  |gs|yr|yr|rp|br|rb|br|pr|                  |pp|gr|ps|pb|pr|ps|bp|rs|                  |gc|rb|gs|pp|bc|gb|rp|pp|                  |rp|gb|rs|ys|yc|yp|rb|bb|" 

here's console app timing:

open system open system.diagnostics  let runtimer runcount title testfunc =     printfn title     let runtimedtest n =          let stopwatch = stopwatch.startnew()         let result = testfunc()         stopwatch.stop()         printfn "%i - %f" n stopwatch.elapsed.totalmilliseconds         result      let results = [| 1..runcount |] |> array.map (fun x -> runtimedtest x)     printfn "%a" results.[0]  let runcount = 1 let gobs = gobstoppers.load testdata.testgrid6  runtimer runcount "getlongestpath" (fun _ -> pathfinder.getlongestpath gobs.neighbormap) runtimer runcount "getlongestpathparallelarray" (fun _ -> pathfinder.getlongestpathparallelarray gobs.neighbormap) runtimer runcount "getlongestpathparallelfor" (fun _ -> pathfinder.getlongestpathparallelfor gobs.neighbormap) runtimer runcount "getlongestpathpseq" (fun _ -> pathfinder.getlongestpathpseq gobs.neighbormap)  let line = console.readline() 

if work scheduled cannot distributed in way can executed in parallel, add overhead when split work.

if work can executed in parallel across multiple cores, or waiting/idle time can used execute task while waiting, when might gain time.

in case calculation there no waiting on io. why code benefit multiple cores (if keep synchronisation low possible)

try executing code on more cores.


Comments