{"id":545,"date":"2008-10-24T18:00:17","date_gmt":"2008-10-24T16:00:17","guid":{"rendered":"http:\/\/www.navision-blog.de\/2008\/10\/24\/using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions-part-ii\/"},"modified":"2008-10-25T10:56:56","modified_gmt":"2008-10-25T08:56:56","slug":"using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions-part-ii","status":"publish","type":"post","link":"http:\/\/www.navision-blog.de\/blog\/2008\/10\/24\/using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions-part-ii\/","title":{"rendered":"Using PLINQ in F# &ndash; Parallel Map and Reduce (Fold) functions &#8211; part 2"},"content":{"rendered":"<p><a href=\"http:\/\/www.navision-blog.de\/2008\/10\/23\/using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions\/\">Last time<\/a> I showed how it is possible to use parallel map and fold functions to compute the sum of all factorials between 1 and 3000. The result was a nearly perfect load balancing for this task on a two processor machine. This time I will derive a generic function that computes partial results in parallel and folds them to a final result.<\/p>\n<p>Let\u2019s consider our F# example:<\/p>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> add a b = a + b&#160; <\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span><strong> <\/strong>fac (x:bigint) = <\/pre>\n<pre style=\"margin: 0px\">  [1I..x] |&gt; List.fold_left (*) 1I<\/pre>\n<pre style=\"margin: 0px\"><strong><span style=\"color: blue\">let<\/span> sequential() =<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160; [1I..3000I]<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160;&#160; |&gt; List.map fac<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160;&#160; |&gt; List.fold_left add 0I<\/strong><\/pre>\n<\/div>\n<p>This is the same as:<\/p>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> calcFactorialSum min max =<\/pre>\n<pre style=\"margin: 0px\">&#160; [min..max] <\/pre>\n<pre style=\"margin: 0px\">&#160;&#160; |&gt; List.map fac<\/pre>\n<pre style=\"margin: 0px\">&#160;&#160; |&gt; List.fold_left add 0I&#160; <\/pre>\n<pre style=\"margin: 0px\">&#160;<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f1() = calcFactorialSum&#160;&#160;&#160; 1I 2000I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f2() = calcFactorialSum 2001I 2200I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f3() = calcFactorialSum 2201I 2400I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f4() = calcFactorialSum 2401I 2600I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f5() = calcFactorialSum 2601I 2800I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f6() = calcFactorialSum 2801I 3000I<\/pre>\n<pre style=\"margin: 0px\">&#160;<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> sequential2() =<\/pre>\n<pre style=\"margin: 0px\">&#160; <strong>f1() + f2() + f3() + f4() + f5() + f6()<\/strong><\/pre>\n<\/div>\n<p>We spitted the summation into 6 independent tasks and computed the sum of the partial results. This has nearly no bearing on the runtime.<\/p>\n<p>But with the help of PLINQ we can compute each task in parallel:<\/p>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> asParallel (list: 'a list) = <\/pre>\n<pre style=\"margin: 0px\">&#160; list.AsParallel&lt;'a&gt;()<\/pre>\n<\/p><\/div>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> runParallel functions = <\/pre>\n<pre style=\"margin: 0px\">&#160;&#160;&#160; ParallelEnumerable.Select(<\/pre>\n<pre style=\"margin: 0px\">      asParallel functions, (<span style=\"color: blue\">fun<\/span> f <span style=\"color: blue\">-&gt;<\/span>&#160; f() ) )<\/pre>\n<pre style=\"margin: 0px\">&#160;<\/pre>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> pFold foldF seed (data:IParallelEnumerable&lt;'a&gt;)=<\/pre>\n<pre style=\"margin: 0px\">&#160; ParallelEnumerable.Aggregate&lt;'a,'b&gt;(<\/pre>\n<pre style=\"margin: 0px\">&#160;&#160;&#160; data, seed, <span style=\"color: blue\">new<\/span> Func&lt;'b,'a,'b&gt;(foldF))<\/pre>\n<pre style=\"margin: 0px\">&#160;<\/pre>\n<\/p><\/div>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><strong><span style=\"color: blue\">let<\/span> calcFactorialsParallel() =<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160; [f1; f2; f3; f4; f5; f6]<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160;&#160;&#160; |&gt; runParallel<\/strong><\/pre>\n<pre style=\"margin: 0px\"><strong>&#160;&#160;&#160; |&gt; pFold add 0I<\/strong><\/pre>\n<\/p><\/div>\n<p><font color=\"#f26722\"><\/font><\/p>\n<\/div>\n<p>This time we build a list of functions (f1, f2, f3, f4, f5, f6) and run them in parallel. &quot;<em>runParallel<\/em>\u201d gives us back a list of the partial results, which we can fold with the function \u201c<em>add<\/em>\u201d to get the final result.<\/p>\n<p>On my Core 2 Duo E6550 with 2.33 GHz and 3.5 GB RAM I get the following results:<\/p>\n<blockquote>\n<p>Time Normal: 26.576s<\/p>\n<p>Time Sequential2: 26.205s (Ratio: 0.99)<\/p>\n<p><strong>Time \u201cParallel Functions\u201d: 18.426s (Ratio: 0.69)<\/strong><\/p>\n<p>Time PLINQ: 14.990s (Ratio: 0.56) (<a href=\"http:\/\/www.navision-blog.de\/2008\/10\/23\/using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions\/\">Last post<\/a>)<\/p>\n<p>Same Results: true<\/p>\n<\/blockquote>\n<p>We can see that the parallel computation of the functions f1 \u2013 f6 is much faster than the sequential.<\/p>\n<p>But why is the PLINQ-version (<a href=\"http:\/\/www.navision-blog.de\/2008\/10\/23\/using-plinq-in-fsharp-parallel-map-and-reduce-fold-functions\/\">see last post<\/a>) still faster? We can easily see that each partial function needs a different runtime (e.g. it\u2019s much harder to calculate the factorials between 2800 and 3000 than between 2000 and 2200). On my machine I get:<\/p>\n<blockquote>\n<p>Time F1: 8.738s<\/p>\n<p>Time F2: 2.663s<\/p>\n<p>Time F3: 3.119s<\/p>\n<p>Time F4: 3.492s<\/p>\n<p>Time F5: 3.889s<\/p>\n<p>Time F6: 4.442s<\/p>\n<\/blockquote>\n<p>The problem is that the Parallel Framework can only guess each runtime amount in advance. So the load balancing for 2 processors will not be optimal in every case. In the original PLINQ-version there are only small tasks, and the difference between each runtime is smaller. So it is easier to compute the load balancing.<\/p>\n<p>But of course we can do better if we split f1 into two functions f7 and f8:<\/p>\n<div style=\"font-size: 10pt; background: white; color: black; font-family: courier new\">\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f7() = calcFactorialSum&#160;&#160;&#160; 1I 1500I<\/pre>\n<pre style=\"margin: 0px\"><span style=\"color: blue\">let<\/span> f8() = calcFactorialSum 1501I 2000I<\/pre>\n<\/div>\n<p>So we can get a better load balancing:<\/p>\n<blockquote>\n<p>Time F1: 8.721s<\/p>\n<p>Time F7: 4.753s<\/p>\n<p>Time F8: 4.829s<\/p>\n<p>Time Normal: 26.137s<\/p>\n<p>Time \u201cParallel Functions\u201d: 16.138s (Ratio: 0.62)<\/p>\n<p>Same Results: true<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Last time I showed how it is possible to use parallel map and fold functions to compute the sum of all factorials between 1 and 3000. The result was a nearly perfect load balancing for this task on a two processor machine. This time I will derive a generic function that computes partial results in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[27,23,448,8,461],"tags":[664,660,464,463,450,462,665],"_links":{"self":[{"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/posts\/545"}],"collection":[{"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/comments?post=545"}],"version-history":[{"count":6,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/posts\/545\/revisions"}],"predecessor-version":[{"id":551,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/posts\/545\/revisions\/551"}],"wp:attachment":[{"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/media?parent=545"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/categories?post=545"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.navision-blog.de\/blog\/wp-json\/wp\/v2\/tags?post=545"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}