<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://msmvps.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Jon Skeet: Coding Blog : Parallelisation</title><link>http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx</link><description>Tags: Parallelisation</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>Revisiting randomness</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/11/04/revisiting-randomness.aspx</link><pubDate>Wed, 04 Nov 2009 07:40:15 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1737577</guid><dc:creator>skeet</dc:creator><slash:comments>18</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1737577</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1737577</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/11/04/revisiting-randomness.aspx#comments</comments><description>&lt;p&gt;Almost every Stack Overflow question which includes the words &amp;quot;random&amp;quot; and &amp;quot;repeated&amp;quot; has the same basic answer. It&amp;#39;s one of the most common &amp;quot;gotchas&amp;quot; in .NET, Java, and no doubt other platforms: creating a new random number generator without specifying a seed will depend on the current instant of time. The current time as measured by the computer doesn&amp;#39;t change very often compared with how often you can create and use a random number generator – so code which repeatedly creates a new instance of Random and uses it once will end up showing a lot of repetition.&lt;/p&gt;  &lt;p&gt;One common solution is to use a static field to store a single instance of Random and reuse it. That&amp;#39;s okay in Java (where Random is thread-safe) but it&amp;#39;s not so good in .NET – if you use the same instance repeatedly from .NET, you can corrupt the internal data structures.&lt;/p&gt;  &lt;p&gt;A long time ago, I created a &lt;a href="http://pobox.com/~skeet/csharp/miscutil/usage/staticrandom.html"&gt;StaticRandom class&lt;/a&gt; in &lt;a href="http://pobox.com/~skeet/csharp/miscutil"&gt;MiscUtil&lt;/a&gt; – essentially, it was just a bunch of static methods (to mirror the instance methods found in Random) wrapping a single instance of Random and locking appropriately. This allows you to just call StaticRandom.Next(1, 7) to roll a die, for example. However, it has a couple of problems:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;It doesn&amp;#39;t scale well in a multi-threaded environment. When I originally wrote it, I benchmarked an alternative approach using [ThreadStatic] and at the time, locking won (at least on my computer, which may well have only had a single core). &lt;/li&gt;    &lt;li&gt;It doesn&amp;#39;t provide any way of getting at an instance of Random, other than by using new Random(StaticRandom.Next()). &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The latter point is mostly a problem because it encourages a style of coding where you just use StaticRandom.Next(…) any time you want a random number. This is undoubtedly convenient in some situations, but it goes against the idea of &lt;a href="http://stackoverflow.com/questions/1667516/doesnt-passing-in-parameters-that-should-be-known-implicitly-violate-encapsulati/1667590#1667590"&gt;treating a source of randomness as a service or dependency&lt;/a&gt;. It makes it harder to get repeatability and to see what needs that dependency.&lt;/p&gt;  &lt;p&gt;I could have just added a method generating a new instance into the existing class, but I decided to play with a different approach – going back to per-thread instances, but this time using the &lt;a href="http://msdn.microsoft.com/en-us/library/dd642243(VS.100).aspx"&gt;ThreadLocal&amp;lt;T&amp;gt; class&lt;/a&gt; introduced in .NET 4.0. Here&amp;#39;s the resulting code:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System;     &lt;br /&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System.Threading;     &lt;br /&gt;    &lt;br /&gt;&lt;span class="Namespace"&gt;namespace&lt;/span&gt; RandomDemo     &lt;br /&gt;{     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// Convenience class for dealing with randomness.&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; ThreadLocalRandom     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// Random number generator used to generate seeds,&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// which are then used to create new random number&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// generators on a per-thread basis.&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; Random globalRandom = &lt;span class="Keyword"&gt;new&lt;/span&gt; Random();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;readonly&lt;/span&gt;&amp;#160;&lt;span class="ReferenceType"&gt;object&lt;/span&gt; globalLock = &lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;#160;&lt;span class="ReferenceType"&gt;object&lt;/span&gt;();     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// Random number generator &lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; ThreadLocal&amp;lt;Random&amp;gt; threadRandom = &lt;span class="Keyword"&gt;new&lt;/span&gt; ThreadLocal&amp;lt;Random&amp;gt;(NewRandom);     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// Creates a new instance of Random. The seed is derived &lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// from a global (static) instance of Random, rather &lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// than time.&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt; Random NewRandom()     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;lock&lt;/span&gt; (globalLock)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;return&lt;/span&gt;&amp;#160;&lt;span class="Keyword"&gt;new&lt;/span&gt; Random(globalRandom.Next());     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// Returns an instance of Random which can be used freely&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// within the current thread.&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt; Random Instance { get { &lt;span class="Statement"&gt;return&lt;/span&gt; threadRandom.Value; } }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;See &amp;lt;see cref=&amp;quot;Random.Next()&amp;quot; /&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;int&lt;/span&gt; Next()     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;return&lt;/span&gt; Instance.Next();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;See &amp;lt;see cref=&amp;quot;Random.Next(int)&amp;quot; /&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;int&lt;/span&gt; Next(&lt;span class="ValueType"&gt;int&lt;/span&gt; maxValue)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;return&lt;/span&gt; Instance.Next(maxValue);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;See &amp;lt;see cref=&amp;quot;Random.Next(int, int)&amp;quot; /&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;int&lt;/span&gt; Next(&lt;span class="ValueType"&gt;int&lt;/span&gt; minValue, &lt;span class="ValueType"&gt;int&lt;/span&gt; maxValue)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;return&lt;/span&gt; Instance.Next(minValue, maxValue);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;See &amp;lt;see cref=&amp;quot;Random.NextDouble()&amp;quot; /&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;double&lt;/span&gt; NextDouble()     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Statement"&gt;return&lt;/span&gt; Instance.NextDouble();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;See &amp;lt;see cref=&amp;quot;Random.NextBytes(byte[])&amp;quot; /&amp;gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;void&lt;/span&gt; NextBytes(&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] buffer)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Instance.NextBytes(buffer);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;} &lt;/div&gt;  &lt;p&gt;The user can still call the static Next(…) methods if they want, but they can also get at the thread-local instance of Random by calling ThreadLocalRandom.Instance – or easily create a new instance with ThreadLocalRandom.NewRandom(). (The fact that NewRandom uses the global instance rather than the thread-local one is an implementation detail really; it happens to be convenient from the point of view of the ThreadLocal&amp;lt;T&amp;gt; constructor. It wouldn&amp;#39;t be terribly hard to change this.)&lt;/p&gt;  &lt;p&gt;Now it&amp;#39;s easy to write a method which needs randomness (e.g. to shuffle a deck of cards) and give it a Random parameter, then call it using the thread-local instance:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;void&lt;/span&gt; Shuffle(Random rng)     &lt;br /&gt;{     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ...     &lt;br /&gt;}     &lt;br /&gt;...     &lt;br /&gt;deck.Shuffle(ThreadLocalRandom.Instance); &lt;/div&gt;  &lt;p&gt;The Shuffle method is then easier to test and debug, and expresses its dependency explicitly.&lt;/p&gt;  &lt;h3&gt;Performance&lt;/h3&gt;  &lt;p&gt;I tested the &amp;quot;old&amp;quot; and &amp;quot;new&amp;quot; implementations in a very simple way – for varying numbers of threads, I called Next() a fixed number of times (from each thread) and timed how long it took for all the threads to finish. I&amp;#39;ve also tried a .NET-3.5-compatible version using ThreadStatic instead of ThreadLocal&amp;lt;T&amp;gt;, and running the original code and the ThreadStatic version on .NET 3.5 as well.&lt;/p&gt;  &lt;table border="1" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td align="right"&gt;Threads&lt;/td&gt;        &lt;td align="right"&gt;StaticRandom (4.0b2)&lt;/td&gt;        &lt;td align="right"&gt;ThreadLocalRandom (4.0b2)&lt;/td&gt;        &lt;td align="right"&gt;ThreadStaticRandom (4.0b2)&lt;/td&gt;        &lt;td align="right"&gt;StaticRandom(3.5)&lt;/td&gt;        &lt;td align="right"&gt;ThreadStaticRandom (3.5)&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td align="right"&gt;1&lt;/td&gt;        &lt;td align="right"&gt;11582&lt;/td&gt;        &lt;td align="right"&gt;6016&lt;/td&gt;        &lt;td align="right"&gt;10150&lt;/td&gt;        &lt;td align="right"&gt;10373&lt;/td&gt;        &lt;td align="right"&gt;16049&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td align="right"&gt;2&lt;/td&gt;        &lt;td align="right"&gt;24667&lt;/td&gt;        &lt;td align="right"&gt;7214&lt;/td&gt;        &lt;td align="right"&gt;9043&lt;/td&gt;        &lt;td align="right"&gt;25062&lt;/td&gt;        &lt;td align="right"&gt;17257&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td align="right"&gt;3&lt;/td&gt;        &lt;td align="right"&gt;38095&lt;/td&gt;        &lt;td align="right"&gt;10295&lt;/td&gt;        &lt;td align="right"&gt;14771&lt;/td&gt;        &lt;td align="right"&gt;36827&lt;/td&gt;        &lt;td align="right"&gt;25625&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td align="right"&gt;4&lt;/td&gt;        &lt;td align="right"&gt;49402&lt;/td&gt;        &lt;td align="right"&gt;13435&lt;/td&gt;        &lt;td align="right"&gt;19116&lt;/td&gt;        &lt;td align="right"&gt;47882&lt;/td&gt;        &lt;td align="right"&gt;34415&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;A few things to take away from this:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The numbers were slightly erratic; somehow it was quicker to do twice the work with ThreadStaticRandom on .NET 4.0b2! This isn&amp;#39;t the perfect benchmarking machine; we&amp;#39;re interested in trends rather than absolute figures. &lt;/li&gt;    &lt;li&gt;Locking hasn&amp;#39;t changed much in performance between framework versions &lt;/li&gt;    &lt;li&gt;ThreadStatic has improved &lt;em&gt;massively&lt;/em&gt; between .NET 3.5 and 4.0 &lt;/li&gt;    &lt;li&gt;Even on 3.5, ThreadStatic wins over a global lock as soon as there&amp;#39;s contention &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The only downside to the ThreadLocal&amp;lt;T&amp;gt; version is that it requires .NET 4.0 - but the ThreadStatic version works pretty well too.&lt;/p&gt;  &lt;p&gt;It&amp;#39;s worth bearing in mind that of course this is testing the highly unusual situation where there&amp;#39;s a &lt;em&gt;lot&lt;/em&gt; of contention in the global lock version. The performance difference in the single-threaded version where the lock is always uncontended is still present, but very small.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;After reading the comments and thinking further, I would indeed get rid of the static methods elsewhere. Also, for the purposes of dependency injection, I agree that it&amp;#39;s a good idea to have a factory interface &lt;em&gt;where that&amp;#39;s not overkill&lt;/em&gt;. The factory implementation could use either the ThreadLocal or ThreadStatic implementations, or effectively use the global lock version (by having its own instance of Random and a lock). In many cases I&amp;#39;d regard that as overkill, however.&lt;/p&gt;  &lt;p&gt;One other interesting option would be to create a thread-safe instance of Random to start with, which delegated to thread-local &amp;quot;normal&amp;quot; implementations. That would be very useful from a DI standpoint.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1737577" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Stack+Overflow/default.aspx">Stack Overflow</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Benchmarking/default.aspx">Benchmarking</category></item><item><title>Iterating atomically</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/10/23/iterating-atomically.aspx</link><pubDate>Fri, 23 Oct 2009 21:20:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1734632</guid><dc:creator>skeet</dc:creator><slash:comments>34</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1734632</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1734632</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/10/23/iterating-atomically.aspx#comments</comments><description>&lt;p&gt;The IEnumerable&amp;lt;T&amp;gt; and IEnumerator&amp;lt;T&amp;gt; interfaces in .NET are interesting. They crop up an awful lot, but hardly anyone ever calls them directly - you almost always use a foreach loop to iterate over the collection. That hides all the calls to GetEnumerator(), MoveNext() and Current. Likewise iterator blocks hide the details when you want to implement the interfaces. However, sometimes details matter - such as for &lt;a href="http://stackoverflow.com/questions/1605745"&gt;this recent Stack Overflow question&lt;/a&gt;. The question asks how to create a thread-safe iterator - one that can be called from multiple threads. This is not about iterating over a collection &lt;em&gt;n&lt;/em&gt; times independently on &lt;em&gt;n&lt;/em&gt; different threads - this is about iterating over a collection &lt;em&gt;once&lt;/em&gt; without skipping or duplicating. Imagine it&amp;#39;s some set of jobs that we have to complete. We assume that the iterator itself is thread-safe to the extent that calls from different threads &lt;em&gt;at different times, with intervening locks&lt;/em&gt; will be handled reasonably. This is reasonable - basically, so long as it isn&amp;#39;t going out of its way to be thread-hostile, we should be okay. We also assume that no-one is trying to write to the collection at the same time.&lt;/p&gt;
&lt;p&gt;Sounds easy, right? Well, no... because the IEnumerator&amp;lt;T&amp;gt; interface has two members which we effectively want to call atomically. In particular, we &lt;em&gt;don&amp;#39;t&lt;/em&gt; want the collection { &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot; } to be iterated like this:&lt;/p&gt;
&lt;table width="400" cellpadding="2" cellspacing="0" border="0"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width="200" valign="top"&gt;Thread 1&lt;/td&gt;
&lt;td width="200" valign="top"&gt;Thread 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width="200" valign="top"&gt;MoveNext()&lt;/td&gt;
&lt;td width="200" valign="top"&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width="200" valign="top"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td width="200" valign="top"&gt;MoveNext()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width="200" valign="top"&gt;Current&lt;/td&gt;
&lt;td width="200" valign="top"&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width="200" valign="top"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td width="200" valign="top"&gt;Current&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That way we&amp;#39;ll end up not processing the first item at all, and the second item twice.&lt;/p&gt;
&lt;p&gt;There are two ways of approaching this problem. In both cases I&amp;#39;ve started with IEnumerable&amp;lt;T&amp;gt; for consistency, but in fact it&amp;#39;s IEnumerator&amp;lt;T&amp;gt; which is the interesting bit. In particular, we&amp;#39;re not going to be able to iterate over our result anyway, as each thread needs to have the same IEnumerator&amp;lt;T&amp;gt; - which it won&amp;#39;t do if each of them uses foreach (which calls GetEnumerator() to start with).&lt;/p&gt;
&lt;h3&gt;Fix the interface&lt;/h3&gt;
&lt;p&gt;First we&amp;#39;ll try to fix the interface to look how it should have looked to start with, at least from the point of view of atomicity. Here are the new interfaces:&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;interface&lt;/span&gt; IAtomicEnumerable&amp;lt;T&amp;gt;     &lt;br /&gt;{     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IAtomicEnumerator&amp;lt;T&amp;gt; GetEnumerator();     &lt;br /&gt;}     &lt;br /&gt;    &lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;interface&lt;/span&gt; IAtomicEnumerator&amp;lt;T&amp;gt;     &lt;br /&gt;{     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;bool&lt;/span&gt; TryMoveNext(&lt;span class="MethodParameter"&gt;out&lt;/span&gt; T nextValue);     &lt;br /&gt;} &lt;/div&gt;
&lt;p&gt;One thing you may notice is that we&amp;#39;re not implementing IDisposable. That&amp;#39;s basically because it&amp;#39;s a pain to do so when you think about a multi-threaded environment. Indeed, it&amp;#39;s possibly one of the biggest arguments against something of this nature. At what point do you dispose? Just because &lt;em&gt;one&lt;/em&gt; thread finished doesn&amp;#39;t mean that the rest of them have... don&amp;#39;t forget that &amp;quot;finish&amp;quot; might mean &amp;quot;an exception was thrown while processing the job, I&amp;#39;m bailing out&amp;quot;. You&amp;#39;d need some sort of co-ordinator to make sure that &lt;em&gt;everyone&lt;/em&gt; is finished before you actually do any clean-up. Anyway, the nice thing about this being a blog post is we can ignore that little thorny issue :)&lt;/p&gt;
&lt;p&gt;The important point is that we now have a single method in IAtomicEnumerator&amp;lt;T&amp;gt; - TryMoveNext, which works the way you&amp;#39;d expect it to. It atomically attempts to move to the next item, returns whether or not it succeeded, and sets an out parameter with the next value if it &lt;em&gt;did&lt;/em&gt; succeed. Now there&amp;#39;s no chance of two threads using the method and stomping on each other&amp;#39;s values (unless they&amp;#39;re silly and use the same variable for the out parameter).&lt;/p&gt;
&lt;p&gt;It&amp;#39;s reasonably easy to wrap the standard interfaces in order to implement this interface:&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&lt;span class="XmlComment"&gt;/// Wraps a normal IEnumerable[T] up to implement IAtomicEnumerable[T].&lt;/span&gt;     &lt;br /&gt;&lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;sealed&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; AtomicEnumerable&amp;lt;T&amp;gt; : IAtomicEnumerable&amp;lt;T&amp;gt;     &lt;br /&gt;{     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; IEnumerable&amp;lt;T&amp;gt; original;     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; AtomicEnumerable(IEnumerable&amp;lt;T&amp;gt; original)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.original = original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; IAtomicEnumerator&amp;lt;T&amp;gt; GetEnumerator()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; AtomicEnumerator(original.GetEnumerator());     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="XmlComment"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="XmlComment"&gt;/// Implementation of IAtomicEnumerator[T] to wrap IEnumerator[T].&lt;/span&gt;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="XmlComment"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;sealed&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; AtomicEnumerator : IAtomicEnumerator&amp;lt;T&amp;gt;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; IEnumerator&amp;lt;T&amp;gt; original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;object&lt;/span&gt; padlock = &lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;object&lt;/span&gt;();     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt; AtomicEnumerator(IEnumerator&amp;lt;T&amp;gt; original)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.original = original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;bool&lt;/span&gt; TryMoveNext(&lt;span class="MethodParameter"&gt;out&lt;/span&gt; T value)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;lock&lt;/span&gt; (padlock)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;bool&lt;/span&gt; hadNext = original.MoveNext();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; value = hadNext ? original.Current : &lt;span class="Modifier"&gt;default&lt;/span&gt;(T);     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; hadNext;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;} &lt;/div&gt;
&lt;p&gt;Just ignore the fact that I never dispose of the original IEnumerator&amp;lt;T&amp;gt; :)&lt;/p&gt;
&lt;p&gt;We use a simple lock to make sure that MoveNext() and Current always happen together - that nothing else is going to call MoveNext() between our TryMoveNext() calling it, and it fetching the current value.&lt;/p&gt;
&lt;p&gt;Obviously you&amp;#39;d need to write your own code to actually &lt;em&gt;use&lt;/em&gt; this sort of iterator, but it would be quite simple:&lt;/p&gt;
&lt;div class="code"&gt;T value;    &lt;br /&gt;&lt;span class="Statement"&gt;while&lt;/span&gt; (iterator.TryMoveNext(&lt;span class="MethodParameter"&gt;out&lt;/span&gt; value))     &lt;br /&gt;{     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Use value&lt;/span&gt;     &lt;br /&gt;} &lt;/div&gt;
&lt;p&gt;However, you may already have code which wants to use an IEnumerator&amp;lt;T&amp;gt;. Let&amp;#39;s see what else we can do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using thread local variables to fake it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;.NET 4.0 has a very useful type called ThreadLocal&amp;lt;T&amp;gt;. It does basically what you&amp;#39;d expect it to, with nice features such as being able to supply a delegate to be executed on each thread to provide the initial value. We can use a thread local to make sure that so long as we call both MoveNext() and Current atomically when we&amp;#39;re asked to move to the next element, we can get back the right value for Current later on. It has to be thread local because we&amp;#39;re sharing a single IEnumerator&amp;lt;T&amp;gt; across multiple threads - each needs its own separate storage.&lt;/p&gt;
&lt;p&gt;This is also the approach we&amp;#39;d use if we wanted to wrap an IAtomicEnumerator&amp;lt;T&amp;gt; in an IEnumerator&amp;lt;T&amp;gt;, by the way. Here&amp;#39;s the code to do it:&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; ThreadSafeEnumerable&amp;lt;T&amp;gt; : IEnumerable&amp;lt;T&amp;gt;     &lt;br /&gt;{     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; IEnumerable&amp;lt;T&amp;gt; original;     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; ThreadSafeEnumerable(IEnumerable&amp;lt;T&amp;gt; original)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.original = original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; IEnumerator&amp;lt;T&amp;gt; GetEnumerator()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; ThreadSafeEnumerator(original.GetEnumerator());     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IEnumerator IEnumerable.GetEnumerator()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; GetEnumerator();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;sealed&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; ThreadSafeEnumerator : IEnumerator&amp;lt;T&amp;gt;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; IEnumerator&amp;lt;T&amp;gt; original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;object&lt;/span&gt; padlock = &lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span class="ReferenceType"&gt;object&lt;/span&gt;();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;readonly&lt;/span&gt; ThreadLocal&amp;lt;T&amp;gt; current = &lt;span class="Keyword"&gt;new&lt;/span&gt; ThreadLocal&amp;lt;T&amp;gt;();     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt; ThreadSafeEnumerator(IEnumerator&amp;lt;T&amp;gt; original)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.original = original;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;bool&lt;/span&gt; MoveNext()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;lock&lt;/span&gt; (padlock)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;bool&lt;/span&gt; ret = original.MoveNext();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;if&lt;/span&gt; (ret)     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; current.Value = original.Current;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; ret;     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; T Current     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { &lt;span class="Statement"&gt;return&lt;/span&gt; current.Value; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Dispose()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; original.Dispose();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; current.Dispose();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ReferenceType"&gt;object&lt;/span&gt; IEnumerator.Current     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { &lt;span class="Statement"&gt;return&lt;/span&gt; Current; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Reset()     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;throw&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; NotSupportedException();     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;} &lt;/div&gt;
&lt;p&gt;I&amp;#39;m going to say it one last time - we&amp;#39;re broken when it comes to disposal. There&amp;#39;s no way of &lt;em&gt;safely&lt;/em&gt; disposing of the original iterator at &amp;quot;just the right time&amp;quot; when everyone&amp;#39;s finished with it. Oh well.&lt;/p&gt;
&lt;p&gt;Other than that, it&amp;#39;s quite simple. This code has the serendipitous property of actually implementing IEnumerator&amp;lt;T&amp;gt; slightly better than C#-compiler-generated implementations from iterator blocks - if you call the Current property without having called MoveNext(), this will throw an InvalidOperationException, just as the documentation says it should. (It doesn&amp;#39;t do the same at the end, admittedly, but that&amp;#39;s fixable if we really wanted to be pedantic.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I found this an intriguing little problem. I think there are better ways of solving the bigger picture - a co-ordinator which takes care of disposing exactly once, and which possibly mediates the original iterator etc is probably the way forward... but I enjoyed thinking about the nitty gritty.&lt;/p&gt;
&lt;p&gt;Generally speaking, I prefer the first of these approaches. Thread local variables always feel like a bit of a grotty hack to me - they can be useful, but it&amp;#39;s better to avoid them if you can. It&amp;#39;s interesting to see how an interface can be inherently thread-friendly or not.&lt;/p&gt;
&lt;p&gt;One last word of warning - this code is &lt;em&gt;completely&lt;/em&gt; untested. It builds, and I can&amp;#39;t immediately see why it wouldn&amp;#39;t work, but I&amp;#39;m making no guarantees...&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1734632" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Wacky+Ideas/default.aspx">Wacky Ideas</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Stack+Overflow/default.aspx">Stack Overflow</category></item><item><title>Recent activities</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/09/04/recent-activities.aspx</link><pubDate>Thu, 03 Sep 2009 23:08:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1720570</guid><dc:creator>skeet</dc:creator><slash:comments>15</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1720570</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1720570</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/09/04/recent-activities.aspx#comments</comments><description>&lt;p&gt;It&amp;#39;s been a little while since I&amp;#39;ve blogged, and quite a lot has been going on. In fact, there are a few things I&amp;#39;d have blogged about already if it weren&amp;#39;t for &amp;quot;things&amp;quot; getting in the way.&lt;/p&gt;
&lt;p&gt;Rather than writing a whole series of very short blog posts, I thought I&amp;#39;d wrap them all up here...&lt;/p&gt;
&lt;h3&gt;C# in Depth: next MEAP drop available soon - Code Contracts&lt;/h3&gt;
&lt;p&gt;Thanks to everyone who gave feedback on my &lt;a href="http://msmvps.com/blogs/jon_skeet/archive/2009/08/05/tricky-decisions-code-contracts-and-parallel-extensions-in-c-in-depth-2nd-edition.aspx"&gt;writing dilemma&lt;/a&gt;. For the moment, the plan is to have a whole chapter about Code Contracts, but &lt;em&gt;not&lt;/em&gt; include a chapter about Parallel Extensions. My argument for making this decision is that Code Contracts really change the &lt;em&gt;feel&lt;/em&gt; of the code, making it almost like a language feature - and its applicability is almost ubiquitous, unlike PFX.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;may&lt;/em&gt; write a PFX chapter as a separate download, but I&amp;#39;m sensitive to those who (like me) appreciate slim books. I don&amp;#39;t want to &amp;quot;bulk out&amp;quot; the book with extra topics.&lt;/p&gt;
&lt;p&gt;The Code Contracts chapter is in the final stages before becoming available to MEAP subscribers. (It&amp;#39;s been &amp;quot;nearly ready&amp;quot; for a couple of weeks, but I&amp;#39;ve been on holiday, amongst other things.) After that, I&amp;#39;m going back to the existing chapters and revising them.&lt;/p&gt;
&lt;h3&gt;Talking in Dublin - C# 4 and Parallel Extensions&lt;/h3&gt;
&lt;p&gt;Last week I gave two talks in Dublin at &lt;a href="http://epicenter.ie/"&gt;Epicenter&lt;/a&gt;. One was on C# 4, and the other on Code Contracts and Parallel Extensions. Both are now available in a slightly odd form on the &lt;a href="http://csharpindepth.com/Talks.aspx"&gt;Talks page&lt;/a&gt; of the C# in Depth web site. I no longer write &amp;quot;formal&amp;quot; PowerPoint slides, so the downloads are for simple bullet points of text, along with silly hand-drawn slides. No code yet - I want to tidy it up a bit before including it.&lt;/p&gt;
&lt;h3&gt;Podcasting with The Connected Show&lt;/h3&gt;
&lt;p&gt;I recently recorded a &lt;a href="http://www.lyalin.com/Blog/archive/2009/09/01/connected-show-15-ndash-c-4-it-ainrsquot-that-complex.aspx"&gt;podcast episode&lt;/a&gt; with &lt;a href="http://www.connectedshow.com/"&gt;The Connected Show&lt;/a&gt;. I&amp;#39;m &amp;quot;on&amp;quot; for the second 2/3 of the show - about an hour of me blathering on about the new features of C# 4. If you can understand generic variance just by listening to me talking about it, you&amp;#39;re a smart cookie ;)&lt;/p&gt;
&lt;p&gt;(Oh, and if you like it, please express your amusement on &lt;a href="http://digg.com/microsoft/Connected_Show_15_Jon_Skeet_goes_DEEP_on_C_4_0"&gt;Digg&lt;/a&gt; / &lt;a href="http://www.dzone.com/links/connected_show_15_jon_skeet_goes_deep_on_c_40.html"&gt;DZone&lt;/a&gt; / &lt;a href="http://dotnetshoutout.com/Connected-Show-15-Jon-Skeet-goes-DEEP-on-C-40"&gt;Shout&lt;/a&gt; / &lt;a href="http://www.dotnetkicks.com/csharp/Connected_Show_15_Jon_Skeet_goes_DEEP_on_C_4_0"&gt;Kicks&lt;/a&gt;.)&lt;/p&gt;
&lt;h3&gt;Finishing up with Functional Programming for the Real World&lt;/h3&gt;
&lt;p&gt;Well, this hasn&amp;#39;t been taking much of my time recently (I bowed out of all the indexing etc!) but &lt;a href="http://manning.com/petricek"&gt;Functional Programming for the Real World&lt;/a&gt; is nearly ready to go. Hard copy should be available in the next couple of months... it&amp;#39;ll be really nice to see how it fares. Much kudos to Tomas for all his hard work - I&amp;#39;ve really just been helping out a little.&lt;/p&gt;
&lt;h3&gt;Starting on Groovy in Action, 2nd edition&lt;/h3&gt;
&lt;p&gt;No sooner does one book finish than another one starts. The &lt;a href="http://manning.com/koenig2/"&gt;second edition of Groovy in Action&lt;/a&gt; is in the works, which should prove interesting. To be honest, I haven&amp;#39;t played with Groovy much since the first edition of the book was finished, so it&amp;#39;ll be interesting to see what&amp;#39;s happened to the language in the meantime. I&amp;#39;ll be applying the same sort of spit and polish that I did in the first edition, and asking appropriately ignorant questions of the other authors.&lt;/p&gt;
&lt;h3&gt;Tech Reviewing C# 4.0 in a Nutshell&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://msmvps.com/blogs/jon_skeet/archive/2008/03/31/book-review-c-3-0-in-a-nutshell.aspx"&gt;I liked C# 3.0 in a Nutshell&lt;/a&gt;, and I feel honoured that Joe asked me to be a tech reviewer for the next edition, which promises to be even better. There&amp;#39;s not a lot more I can say about it at the moment, other than it&amp;#39;ll be out in 2010 - and I still feel that C# in Depth is a good companion book.&lt;/p&gt;
&lt;h3&gt;MoreLINQ now at 1.0 beta&lt;/h3&gt;
&lt;p&gt;A while ago I started the &lt;a href="http://code.google.com/p/morelinq/"&gt;MoreLINQ project&lt;/a&gt;, and it gained some developers with more time than I&amp;#39;ve got available :) Basically the idea is to add some more useful LINQ extension methods to LINQ to Object. Thanks to Atif Aziz, the first beta version has been released. This doesn&amp;#39;t mean we&amp;#39;re &amp;quot;done&amp;quot; though - just that we think we&amp;#39;ve got something useful. Any suggestions for other operators would be welcome.&lt;/p&gt;
&lt;h3&gt;Manning Pop Quiz and discounts&lt;/h3&gt;
&lt;p&gt;While I&amp;#39;m plugging books etc, it&amp;#39;s worth mentioning the &lt;a href="http://www.manning.com/popquiz/"&gt;Manning Pop Quiz&lt;/a&gt; - multiple choice questions on a wide variety of topics. Fabulous prizes available, as well as one-day discounts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monday, Sept 7th: 50% of all print books (code: pop0907)&lt;/li&gt;
&lt;li&gt;Monday, Sept 14: 50% off all ebooks&amp;nbsp; (code: pop0914)&lt;/li&gt;
&lt;li&gt;Thursday, Sept 17: $25 for C# in Depth, 2nd Edition MEAP print version (code: pop0917) + C# Pop Quiz question&lt;/li&gt;
&lt;li&gt;Monday, Sept 21: 50% off all books&amp;nbsp; (code: pop0921)&lt;/li&gt;
&lt;li&gt;Thursday, Sept 24: $12 for C# in Depth, 2nd Edition MEAP ebook (code: pop0924) + another C# Pop Quiz question&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Future speaking engagements&lt;/h3&gt;
&lt;p&gt;On September 16th I&amp;#39;m going to be speaking to &lt;a href="http://edgeug.net/"&gt;Edge UG&lt;/a&gt; (formerly Vista Squad) in London about Code Contracts and Parallel Extensions. I&amp;#39;m already &lt;em&gt;very&lt;/em&gt; much looking forward to the &lt;a href="http://stackoverflow.carsonified.com/events/london/"&gt;Stack Overflow DevDays London conference&lt;/a&gt; on October 28th, at which I&amp;#39;ll be talking about how humanity has screwed up computing.&lt;/p&gt;
&lt;h3&gt;Future potential blog posts&lt;/h3&gt;
&lt;p&gt;Some day I may get round to writing about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Revisiting StaticRandom with ThreadLocal&amp;lt;T&amp;gt;&lt;/li&gt;
&lt;li&gt;Volatile doesn&amp;#39;t mean what I thought it did&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;#39;s a lot more writing than coding in that list... I&amp;#39;d like to spend some more time on &lt;a href="http://code.google.com/p/minibench/"&gt;MiniBench&lt;/a&gt; at some point, but you know what deadlines are like.&lt;/p&gt;
&lt;p&gt;Anyway, that&amp;#39;s what I&amp;#39;ve been up to and what I&amp;#39;ll be doing for a little while...&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1720570" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/General/default.aspx">General</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Books/default.aspx">Books</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_+4/default.aspx">C# 4</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Speaking+engagements/default.aspx">Speaking engagements</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Stack+Overflow/default.aspx">Stack Overflow</category></item><item><title>Buffering vs streaming benchmark: first results</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/03/24/buffering-vs-streaming-benchmark-first-results.aspx</link><pubDate>Tue, 24 Mar 2009 01:34:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1681085</guid><dc:creator>skeet</dc:creator><slash:comments>11</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1681085</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1681085</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/03/24/buffering-vs-streaming-benchmark-first-results.aspx#comments</comments><description>&lt;p&gt;My poor laptop&amp;#39;s had a busy weekend. It&amp;#39;s run 72 tests, rebooting between each test. Most of these tests have kept both the CPU and disk busy for a lot of the time. I expect to update this blog post with more numbers - and possibly more strategies - as time goes on, but I wanted to get the numbers out as quickly as possible. Graphs should be coming when I&amp;#39;ve got a decent network to experiment with Google graphing.&lt;/p&gt;
&lt;h3&gt;Source code and setup&lt;/h3&gt;
&lt;p&gt;While I don&amp;#39;t really expect anyone else to sacrifice their computer for the best part of 24 hours, it doesn&amp;#39;t take &lt;em&gt;very&lt;/em&gt; much effort to get the tests running. &lt;a href="http://pobox.com/~skeet/csharp/blogfiles/BufferingAndStreaming.zip"&gt;This zip file&lt;/a&gt; contains the source code for three programs, a batch file to generate the test data, and a command file.&lt;/p&gt;
&lt;p&gt;The CreateFiles executable creates input files based on its command line arguments. (This is run multiple times by the batch file.) EncryptFiles &amp;quot;encrypts&amp;quot; (I use the term loosely) all the files in a given directory, writing the results to another directory. The class design of EncryptFiles is a little bit funny, but it&amp;#39;s really tailored for these tests. It should be easy to write other strategies in the same way too.&lt;/p&gt;
&lt;p&gt;ExecuteAndReboot is used to automate the testing. Basically it reads a command file (from a hard-coded directory; simplicity trumps flexibility here). If the file isn&amp;#39;t empty, it waits five minutes (so that after a reboot the computer has time to settle down after loading all its services) and runs the command specified in the first line of the file, appending the output to a results file. When the command has finished executing, ExecuteAndReboot rewrites the command file, removing the first line, and schedules a reboot. The idea is to drop ExecuteAndReboot into a Startup folder, set Windows to log in automatically, reboot once and then just let it go. If you want to use the computer, just wait until it&amp;#39;s in the &amp;quot;waiting five minutes&amp;quot; bit at the start of the cycle and kill ExecuteAndReboot. Next time you reboot it will start testing again.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s the general principle - if anyone &lt;em&gt;does&lt;/em&gt; want to run the tests on their mail and needs a bit of help setting it up, just let me know.&lt;/p&gt;
&lt;h3&gt;Test environment and workload&lt;/h3&gt;
&lt;p&gt;I expect the details of the execution environment to affect the outcome significantly. For the sake of the record then, my laptop specs are:&lt;/p&gt;
&lt;p&gt;Dell Inspiron 1720 (unlikely to be relevant, but...)&lt;br /&gt;Intel Core 2 Duo T7250 2.0GHz&lt;br /&gt;Vista Home Premium 32 bit &lt;br /&gt;3 GB main memory, 32K L1 cache, 2048K L2 cache&lt;br /&gt;2 disks, both 120GB (Fujitsu MHY2120BH, 5400RPM, 8MB cache)&lt;/p&gt;
&lt;p&gt;Even though there are two drives in the system, the tests only ever read from and wrote to a single disk.&lt;/p&gt;
&lt;p&gt;I created four sets of input data; I&amp;#39;ve presented the results for each one separately below, and included the description of the files along with the results. Basically in each case there&amp;#39;s a set of files where each file is the same size, consisting of a fixed number of fixed-length lines.&lt;/p&gt;
&lt;p&gt;The &amp;quot;encryption&amp;quot; that is performed in each case (on the encoded version of the line) is just to XOR each byte with a value. This is repeated a specified number of times, and the XOR value on each pass is the current index, i.e. it first XORs with 0, then 1, then 2 etc. I&amp;#39;ve used work levels of 0 (the loop never runs), 100 and 1000. These were picked somewhat arbitrarily, but they give interesting results. At 0 the process is clearly IO-bound. At 1000 it&amp;#39;s clearly CPU-bound. At 100 it&amp;#39;s not pegging the CPU, but there&amp;#39;s significant activity. I may try 200 as well at some point.&lt;/p&gt;
&lt;p&gt;For each scenario I tried using 1, 2 and 3 threads (and 4 for the big tests; I&amp;#39;ll go back and repeat earlier ones with 4 threads soon). The two strategies used are &amp;quot;streaming&amp;quot;: read a line, encrypt, write the line, repeat and &amp;quot;buffering&amp;quot;: read a whole file, encrypt it all, write it all out. After a thread has started, it needs no shared data at all - it&amp;#39;s given the complete list of files to encrypt at the very start.&lt;/p&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;For every table, the number of threads is shown by the leftmost column, and the work level is shown by the topmost row. The cells are of the form &amp;quot;x/y&amp;quot; where &amp;quot;x&amp;quot; is the time taken for the streaming strategy, and &amp;quot;y&amp;quot; is the time taken for the buffering strategy. All times are in seconds. For each column, I&amp;#39;ve highlighted the optimal test.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Small tests&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are 10000 test files. Each is 100 lines long, with 100 characters per line.&lt;/p&gt;
&lt;table&gt;

&lt;tr align="right"&gt;
&lt;td&gt;Threads/Work&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47&lt;/strong&gt;/90&lt;/td&gt;
&lt;td&gt;94/&lt;strong&gt;79&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;163/158&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;95/112&lt;/td&gt;
&lt;td&gt;113/157&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;129&lt;/strong&gt;/146&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;179/128&lt;/td&gt;
&lt;td&gt;130/144&lt;/td&gt;
&lt;td&gt;152/160&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;144/139&lt;/td&gt;
&lt;td&gt;163/193&lt;/td&gt;
&lt;td&gt;156/204&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Medium tests (short lines)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are 100 test files. Each is 500,000 lines long, with 20 characters per line.&lt;/p&gt;
&lt;table&gt;

&lt;tr align="right"&gt;
&lt;td&gt;Threads/Work&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;138&lt;/strong&gt;/157&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;219&lt;/strong&gt;/267&lt;/td&gt;
&lt;td&gt;1125/1205&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;196/238&lt;/td&gt;
&lt;td&gt;259/263&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;604&lt;/strong&gt;/691&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;292/250&lt;/td&gt;
&lt;td&gt;312/236&lt;/td&gt;
&lt;td&gt;624/676&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;302/291&lt;/td&gt;
&lt;td&gt;321/281&lt;/td&gt;
&lt;td&gt;602/666&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Medium tests (long lines)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are 100 test files. Each is 100 lines long, with 100,000 characters per line.&lt;/p&gt;
&lt;table&gt;

&lt;tr align="right"&gt;
&lt;td&gt;Threads/Work&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;203&lt;/strong&gt;/213&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;249&lt;/strong&gt;/286&lt;/td&gt;
&lt;td&gt;1027/1107&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;283/264&lt;/td&gt;
&lt;td&gt;311/296&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;602&lt;/strong&gt;/696&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;319/284&lt;/td&gt;
&lt;td&gt;314/278&lt;/td&gt;
&lt;td&gt;608/618&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;360/300&lt;/td&gt;
&lt;td&gt;336/297&lt;/td&gt;
&lt;td&gt;607/598&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Big tests&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are 20 files. Each file is 100,000 lines long, with 1000 characters per line.&lt;/p&gt;
&lt;table&gt;

&lt;tr align="right"&gt;
&lt;td&gt;Threads/Work&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;190&lt;/strong&gt;/246&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;349&lt;/strong&gt;/441&lt;/td&gt;
&lt;td&gt;2047/2165&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;365/375&lt;/td&gt;
&lt;td&gt;392/473&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1097&lt;/strong&gt;/1387&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;456/379&lt;/td&gt;
&lt;td&gt;484/399&lt;/td&gt;
&lt;td&gt;1161/1296&lt;/td&gt;
&lt;/tr&gt;
&lt;tr align="right"&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;517/442&lt;/td&gt;
&lt;td&gt;521/431&lt;/td&gt;
&lt;td&gt;1113/1214&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;h3&gt;Conclusions&lt;/h3&gt;
&lt;p&gt;I&amp;#39;m loathe to draw &lt;i&gt;too&lt;/i&gt; many conclusions so far. After all, we&amp;#39;ve only got data for a single test environment. It would be interesting to see what would happen on a quad core machine. However, I think a few things can be said with reasonable confidence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sometimes the streaming strategy wins, sometimes the buffering strategy wins. The difference can be quite significant either way. For a given input and workload, the streaming solution almost always wins &lt;em&gt;on my machine&lt;/em&gt;.  &lt;/li&gt;
&lt;li&gt;In ideal scenario, the bottlenecked resource should be busy for as much of the time as possible. For example, in a high-CPU task it makes little sense to have idle cores when you&amp;#39;ve already loaded data. Likewise in a disk-bound task we should always be fetching (or writing) data even if all our cores are momentarily busy. Personally I think this is the operating system&amp;#39;s pre-fetch systems&amp;#39;s job. We could do it ourselves, but it&amp;#39;s nicer if we don&amp;#39;t have to.  &lt;/li&gt;
&lt;li&gt;If we&amp;#39;re disk-bound, it makes sense to read (or write) one file at a time, to avoid seeking. This is obvious from the results - for work levels of 0 and 100, a single-thread solution is consistently best (and streaming usually works better buffering). Based on this assumption, I suspect a cleaner solution would be to have a single thread reading largish chunks (but not the whole file) and feeding the other threads - or to use async IO. However, this all gets very complicated. One nice aspect of both of the current solutions is that they&amp;#39;re really simple to implement safely. Reading line-by-line in an async manner is a pain. I&amp;#39;d quite like to write an async &amp;quot;execute for every line&amp;quot; helper at some time, but not just yet.  &lt;/li&gt;
&lt;li&gt;If we&amp;#39;re CPU bound, the buffering solution&amp;#39;s sweet spot is 3 threads (remember we&amp;#39;re using 2 cores) and the streaming solution works better with just 2 threads - introducing more threads will make both the IO and context switching less efficient.  &lt;/li&gt;
&lt;li&gt;The streaming strategy &lt;i&gt;always&lt;/i&gt; uses less application memory than the buffering strategy. When running the buffering strategy against the &amp;quot;big&amp;quot; data set with 4 threads, the memory varied between 800MB and 1.8GB (as measured by the &amp;quot;Total bytes in all heaps&amp;quot; performance counter), vs around 165KB with the streaming strategy. (Obviously the process itself took more than 165KB, but that was the memory used in &amp;quot;normal&amp;quot; managed heap memory.) Using more memory to improve performance is a valid technique, but I&amp;#39;d say it&amp;#39;s only worth it when the improvement is very significant. Additionally, the streaming technique instantly scales to huge data files. While they&amp;#39;re not in the current requirements, it doesn&amp;#39;t hurt to get that ability for free.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Next steps...&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve included quite a few &amp;quot;I&amp;#39;m going to try to [...]&amp;quot; points in this post. Just for reference, my plans are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Turn off my virus checker! If this makes a significant difference, I&amp;#39;ll rerun all the current tests &lt;/li&gt;
&lt;li&gt;Try to visualise the results with Google graphs - although I think it may get cluttered &lt;/li&gt;
&lt;li&gt;Rerun big/buffering/1000 test - odd results &lt;/li&gt;
&lt;li&gt;Rerun tests with a work level of 200 and keep an eye on CPU usage - I&amp;#39;d like to spot where we tip between CPU and IO being the bottleneck  &lt;/li&gt;
&lt;li&gt;Try to optimize the Stream/StreamReader being used - we should be able to skip the FileStream buffer completely, and specifying FileOptions.SequentialScan should give more hints to Windows about how we&amp;#39;re intending to read the data. I&amp;#39;ll try a few different buffer sizes, probably 4K, 8K and 64K, just to see what happens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a really interesting little example problem, because it sounds so simple (and it &lt;em&gt;is&lt;/em&gt; simple in terms of the implementation) but it has a lot of environment variables. Of course it would be nicer if we didn&amp;#39;t have to reboot the machine between test runs in order to get a fair result, but never mind...&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1681085" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Benchmarking/default.aspx">Benchmarking</category></item><item><title>Benchmarking IO: buffering vs streaming</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/03/20/benchmarking-io-buffering-vs-streaming.aspx</link><pubDate>Fri, 20 Mar 2009 11:13:31 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1679878</guid><dc:creator>skeet</dc:creator><slash:comments>24</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1679878</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1679878</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/03/20/benchmarking-io-buffering-vs-streaming.aspx#comments</comments><description>&lt;p&gt;I mentioned in my recent book review that I was concerned about a recommendation to load all of the data from an input file before processing all of it. This seems to me to be a bad idea in an age where Windows prefetch will anticipate what data you need next, etc - allowing you to process efficiently in a streaming fashion.&lt;/p&gt;  &lt;p&gt;However, without any benchmarks I&amp;#39;m just guessing. I&amp;#39;d like to set up a benchmark to test this - it&amp;#39;s an interesting problem which I suspect has lots of nuances. This isn&amp;#39;t about trying to prove the book wrong - it&amp;#39;s about investigating a problem which &lt;em&gt;sounds&lt;/em&gt; relatively simple, but could well not be. I wouldn&amp;#39;t be at all surprised to see that in some cases the streaming solution is faster, and in other cases the buffered solution is faster.&lt;/p&gt;  &lt;h3&gt;The Task&lt;/h3&gt;  &lt;p&gt;The situation presented is like this:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;We have a bunch of input files, either locally or on the network (I&amp;#39;m probably just going to test locally for now)&lt;/li&gt;    &lt;li&gt;Each file is less than 100MB&lt;/li&gt;    &lt;li&gt;We need to encrypt each line of text in each input file, writing it to a corresponding output file&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The method suggested in the book is for each thread to:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Load a file into a List&amp;lt;string&amp;gt;&lt;/li&gt;    &lt;li&gt;Encrypt every line (replacing it in the list)&lt;/li&gt;    &lt;li&gt;Save to a new file&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;My alternative option is:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Open a TextReader and a TextWriter for the input/output&lt;/li&gt;    &lt;li&gt;Repeatedly read a line, encrypt, write the encrypted line until we&amp;#39;ve exhausted the input file&lt;/li&gt;    &lt;li&gt;Close both the reader and the writer&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;These are the two implementations I want to test. I strongly suspect that the &lt;em&gt;optimal &lt;/em&gt;solution would involve async IO, but doing an async version of ReadLine is a real pain for various reasons. I&amp;#39;m going to keep it simple - using plain threading, no TPL etc.&lt;/p&gt;  &lt;p&gt;I haven&amp;#39;t written any code yet. This is where you come in - not to write the code for me, but to make sure I test in a useful way.&lt;/p&gt;  &lt;h3&gt;Environmental variations&lt;/h3&gt;  &lt;p&gt;My plan of attack is to first write a small program to generate the input files. These will just be random text files, and the program will have a few command line parameters:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Directory to put files under (one per test variation, basically)&lt;/li&gt;    &lt;li&gt;Number of files to create&lt;/li&gt;    &lt;li&gt;Number of lines per file&lt;/li&gt;    &lt;li&gt;Number of characters per line&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;I&amp;#39;ll probably test a high and a low number for each of the last three parameters, possibly omitting a few variations for practical reasons.&lt;/p&gt;  &lt;p&gt;In an ideal world I&amp;#39;d test on several different computers, locally and networked, but that just isn&amp;#39;t practical. In particular I&amp;#39;d be interested to see how much difference an SSD (low seek time) makes to this test. I&amp;#39;ll be using my normal laptop, which is a dual core Core Duo with two normal laptop disks. I may well try using different drives for reading and writing to see how much difference that makes.&lt;/p&gt;  &lt;h3&gt;Benchmarking&lt;/h3&gt;  &lt;p&gt;The benchmark program will also have a few command line parameters:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Directory to read files from&lt;/li&gt;    &lt;li&gt;Directory to write files to&lt;/li&gt;    &lt;li&gt;Number of threads to use (in some cases I &lt;em&gt;suspect&lt;/em&gt; that more threads than cores will be useful, to avoid cores idling while data is read for a blocking thread)&lt;/li&gt;    &lt;li&gt;Strategy to use (buffered or streaming)&lt;/li&gt;    &lt;li&gt;Encryption work level&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The first three parameters here are pretty self-explanatory, but the encryption work level isn&amp;#39;t. Basically I want to be able to vary the difficulty of the task, which will vary whether it ends up being CPU-bound or IO-bound (I expect). So, for a particular line I will:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Convert to binary (using Encoding.ASCII - I&amp;#39;ll generate just ASCII files)&lt;/li&gt;    &lt;li&gt;Encrypt the binary data&lt;/li&gt;    &lt;li&gt;Encrypt the encrypted binary data&lt;/li&gt;    &lt;li&gt;Encrypt the encrypted encrypted [...] etc until we&amp;#39;ve hit the number given by the encryption work level&lt;/li&gt;    &lt;li&gt;Base64 encode the result - this will be the output line&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;So with an encryption work level of 1 I&amp;#39;ll just encrypt once. With a work level of 2 I&amp;#39;ll encrypt twice, etc. This is purely for the sake of giving the computer something to do. I&amp;#39;ll use AES unless anyone has a better suggestion. (Another option would be to just use an XOR or something else incredibly simple.) The key/IV will be fixed for all tests, just in case that has a bearing on anything.&lt;/p&gt;  &lt;p&gt;The benchmarking program is going to be as simple as I can possibly make it:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Start a stopwatch&lt;/li&gt;    &lt;li&gt;Read the names of all the files in the directory&lt;/li&gt;    &lt;li&gt;Create a list of files for each thread to encrypt&lt;/li&gt;    &lt;li&gt;Create and start the threads&lt;/li&gt;    &lt;li&gt;Use Thread.Join on all the threads&lt;/li&gt;    &lt;li&gt;Stop the stopwatch and report the time taken&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;No rendezvous required at all, which certainly simplifies things. By creating the work list before the thread, I don&amp;#39;t need to worry about memory model issues. It should all just be fine.&lt;/p&gt;  &lt;p&gt;In the absence of a better way of emptying all the file read caches (at the Windows and disk levels) I plan to reboot my computer between test runs (which makes it pretty expensive in terms of time spent - hence omitting some variations). I wasn&amp;#39;t planning on shutting services etc down: I really hope that Vista won&amp;#39;t do anything silly like trying to index the disk while I&amp;#39;ve got a heavy load going. Obviously I won&amp;#39;t run any other applications at the same time.&lt;/p&gt;  &lt;p&gt;If anyone has any suggested changes, I&amp;#39;d be very glad to hear them. Have I missed anything? Should I run a test where the file sizes vary? Is there a better way of flushing all caches than rebooting?&lt;/p&gt;  &lt;p&gt;I don&amp;#39;t know exactly when I&amp;#39;m going to find time to do all of this, but I&amp;#39;ll get there eventually :)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1679878" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Benchmarking/default.aspx">Benchmarking</category></item><item><title>Book Review: C# 2008 and 2005 Threaded Programming: Beginner's Guide by Gastón Hillar</title><link>http://msmvps.com/blogs/jon_skeet/archive/2009/03/16/book-review-c-2008-and-2005-threaded-programming-beginner-s-guide-by-gast-243-n-hillar.aspx</link><pubDate>Mon, 16 Mar 2009 17:21:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1678753</guid><dc:creator>skeet</dc:creator><slash:comments>28</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1678753</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1678753</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2009/03/16/book-review-c-2008-and-2005-threaded-programming-beginner-s-guide-by-gast-243-n-hillar.aspx#comments</comments><description>&lt;h3&gt;Update (19th March 2009)&lt;/h3&gt;
&lt;p&gt;Debate around this review is getting heated. I stand by all the points I make about the text, but I&amp;#39;d like to clarify a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If there are any ad hominem comments in the review against the author, please ignore them. I&amp;#39;m going to try to weed out any that I find, but if you spot one, please let me know and then ignore it. I feel very strongly that a review should be about the &lt;em&gt;text&lt;/em&gt;&amp;nbsp;of a book, not about its&amp;nbsp;&lt;em&gt;author&lt;/em&gt;. The text is what will inform the reader, not the author&amp;#39;s other work. I&amp;#39;m aware that&amp;nbsp;Gast&amp;oacute;n Hillar has written many other books, and is generally well-regarded (as far as I can tell, anyway). That neither helps nor hinders the text. The same goes for me and the review, of course. Whether you know me or not, whether you&amp;#39;ve read anything else I&amp;#39;ve written or not, the review should stand on its own merits. This is not a popularity contest - it&amp;#39;s a discussion about a &lt;em&gt;technical&lt;/em&gt;&amp;nbsp;book.&lt;/li&gt;
&lt;li&gt;The impression I&amp;#39;ve given in the review is almost entirely negative. This is because that&amp;#39;s the impression I received as a reader interested in accuracy and best practices. That &lt;em&gt;does not&lt;/em&gt;&amp;nbsp;mean that the book is entirely inaccurate - far from it. There are plenty of aspects where I have no particular issues with the accuracy. (The code style is more uniformly disagreeable &lt;em&gt;to me&lt;/em&gt;, but that&amp;#39;s a subjective matter.) However, there is enough inaccuracy (and bad practice, in my view - somewhat subjective, but less so than the code style) to make that the dominant impression left with me, alongside my surprise that there&amp;#39;s no proper discussion of locking. As an analogy, imagine you go to a choral concert. Suppose the sopranos, altos and tenors are all perfectly in tune, but the basses are out of key the whole time. In some senses the concert would be 75% accurate - but the 25% inaccuracy would be enough to ruin it. So it is with technical books (not just this one) - it only takes a relatively small degree of inaccuracy to make the difference between a good book and a bad one. The bottom line is: even I don&amp;#39;t think everything or even &lt;em&gt;most&lt;/em&gt;&amp;nbsp;of what&amp;#39;s written in the book is wrong; there are enough problems to make me dislike it though.&lt;/li&gt;
&lt;li&gt;I&amp;#39;ve made a few minor edits to the review just now, to address a few comments made so far. If some of the comments appear to be odd, that may be why!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.packtpub.com/beginners-guide-for-C-sharp-2008-and-2005-threaded-programming/book"&gt;Publisher&amp;#39;s page&lt;/a&gt; (Packt) - this is the cheapest way to buy the book as far as I can see  &lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.packtpub.com/files/code/7108_Code.zip"&gt;Sample code&lt;/a&gt; (49MB download! Mostly because it contains bin/obj directories for all solutions...)  &lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.amazon.com/2008-2005-Threaded-Programming-Beginners/dp/1847197108"&gt;Amazon&lt;/a&gt; or &lt;a href="http://search.barnesandnoble.com/C-2008-and-2005-Threaded-Programming/Gaston-C-Hillar/e/9781847197108/?itm=1"&gt;Barnes and Noble&lt;/a&gt; links if you don&amp;#39;t want to buy it directly &lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.devsource.com/c/a/Techniques/Book-Review-C-2008-and-2005-Threaded-Programming/"&gt;John Mueller&amp;#39;s review&lt;/a&gt; - a much more positive review than this one, which may prove an interesting counterbalance for readers. (Thanks to Erik for pointing out John&amp;#39;s review in the comments.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Disclaimer&lt;/h3&gt;
&lt;p&gt;This book doesn&amp;#39;t really compete with C# in Depth, but obviously the very fact that it&amp;#39;s another book about C# at all means I&amp;#39;m probably not entirely unbiased. Arguably it also &amp;quot;competes&amp;quot; with my own (somewhat out of date now) threading article, although that&amp;#39;s not a monetary venture for me. I should also point out that my copy was sent to me for free, specifically for review, by Packt Publishing.&lt;/p&gt;
&lt;h3&gt;Audience and content&lt;/h3&gt;
&lt;p&gt;The book claims that &amp;quot;Where you are a beginner to working with threads or an old hand who is looking for a reference, this book should be on your desk.&amp;quot; In practice, I don&amp;#39;t think it&amp;#39;s really suitable as a reference. The kind of information you really want as a reference is hard to find amidst the bulk of the book, which is on-going examples. For the rest of this review I&amp;#39;ll regard the intended audience as just beginners.&lt;/p&gt;
&lt;p&gt;The first chapter (out of 12; at 388 pages one of the nice things about this books it that it&amp;#39;s relatively slim) is introductory material about threads and processes, and why concurrency is important in the first place. After this one code-free chapter, the rest of the book is all example-based. The pattern goes something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Give rough idea of what we&amp;#39;re building  &lt;/li&gt;
&lt;li&gt;Create first version of the code  &lt;/li&gt;
&lt;li&gt;Explain what it does and why it may not be ideal  &lt;/li&gt;
&lt;li&gt;Improve it  &lt;/li&gt;
&lt;li&gt;Explain how the improvements work  &lt;/li&gt;
&lt;li&gt;Move to next example or add new major feature &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That sounds all very well, but I&amp;#39;ll get to my issues with it in a minute. Although the examples are constantly evolving, they essentially break down into these applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Code cracking&amp;quot; (brute-forcing a 4 character code)  &lt;/li&gt;
&lt;li&gt;&amp;quot;Encrypting&amp;quot; SMS messages (not real encryption - no key - but a general CPU-intensive transformation)  &lt;/li&gt;
&lt;li&gt;Image processing to find and highlight &amp;quot;old stars&amp;quot; in NASA images  &lt;/li&gt;
&lt;li&gt;&amp;quot;Encrypting&amp;quot; several files  &lt;/li&gt;
&lt;li&gt;More image processing - adjusting the brightness of a large image and thumbnailing it &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all Windows Forms applications, and are frankly pretty similar, all basically dealing with simple, &lt;a href="http://en.wikipedia.org/wiki/Embarrassingly_parallel"&gt;embarrassingly parallel&lt;/a&gt; tasks. That&amp;#39;s not to say Hillar doesn&amp;#39;t get a fair set of different techniques and lessons out of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeping the UI thread free (and seeing what happens when you don&amp;#39;t)  &lt;/li&gt;
&lt;li&gt;Tips for debugging multi-threaded apps in Visual Studio  &lt;/li&gt;
&lt;li&gt;Showing the performance for individual processes using Task Manager and Windows Explorer  &lt;/li&gt;
&lt;li&gt;Using BackgroundWorker to update the UI  &lt;/li&gt;
&lt;li&gt;Queuing tasks in the system thread pool  &lt;/li&gt;
&lt;li&gt;Creating new threads explicitly  &lt;/li&gt;
&lt;li&gt;Using Control.Invoke/BeginInvoke to update the UI (although this comes &lt;i&gt;very&lt;/i&gt; late in the book - chapter 10 out of 12)  &lt;/li&gt;
&lt;li&gt;Keeping tasks independent  &lt;/li&gt;
&lt;li&gt;Noting that sharing data between threads is difficult - but coming to the wrong conclusions (more later)  &lt;/li&gt;
&lt;li&gt;Using the Timer component (just the WinForms timer; not System.Threading.Timer, System.Web.UI.Timer or System.Timers.Timer) - although later on he uses a BackgroundWorker for a task much more suitable for a Timer.  &lt;/li&gt;
&lt;li&gt;A bit of OO design, although in a pretty botched way - the idea of having a general-purpose &amp;quot;parallel algorithm&amp;quot; class and a &amp;quot;parallel algorithm piece&amp;quot; class is reasonable, but it isn&amp;#39;t handled nearly as well as it might be  &lt;/li&gt;
&lt;li&gt;Fairly disastrous advice (IMO) about both I/O and the GC  &lt;/li&gt;
&lt;li&gt;Exception &amp;quot;handling&amp;quot; (where &amp;quot;swallowing exceptions and just reporting them with Debug.Print&amp;quot; counts as &amp;quot;handling&amp;quot; apparently)  &lt;/li&gt;
&lt;li&gt;Parallel Extensions from .NET 4.0, with both PLINQ and TPL &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, this misses out some of the most important concepts in parallelism on .NET. Hillar frequently &lt;i&gt;mentions&lt;/i&gt; locking, but only ever in a &amp;quot;we&amp;#39;re avoiding doing it&amp;quot; way. I find it absolutely incredible that a book on multi-threading in C# doesn&amp;#39;t even &lt;i&gt;mention&lt;/i&gt; the &amp;quot;lock&amp;quot; keyword. Okay, it&amp;#39;s nice to be able to split tasks up completely independently where possible, but in the real world you sometimes &lt;i&gt;have&lt;/i&gt; to use shared mutable state (or at least, it&amp;#39;s often the simplest approach).&lt;/p&gt;
&lt;p&gt;When I first got the book, I looked up several entries in the index to see how they&amp;#39;d be handled. I was shocked to find that &lt;i&gt;none&lt;/i&gt; of these have an index entry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lock  &lt;/li&gt;
&lt;li&gt;volatile  &lt;/li&gt;
&lt;li&gt;memory model  &lt;/li&gt;
&lt;li&gt;Monitor  &lt;/li&gt;
&lt;li&gt;Wait or Pulse  &lt;/li&gt;
&lt;li&gt;BeginInvoke or Invoke  &lt;/li&gt;
&lt;li&gt;double-checked locking  &lt;/li&gt;
&lt;li&gt;mutable or immutable  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The concept of accessing state from multiple threads is glossed over for the entirety of the book. Basically whenever multiple threads want to make their results available, they put them in different elements of an array or list. There&amp;#39;s an &lt;i&gt;assumption&lt;/i&gt; that if you read from that array/list in a different thread, it&amp;#39;s all okay. Likewise there&amp;#39;s an &lt;i&gt;assumption&lt;/i&gt; that it&amp;#39;s appropriate to read integer variables written to in one thread from another thread without any locking, volatility or use of the Interlocked class. I&amp;#39;ll come back to this topic when I tackle accuracy later on.&lt;/p&gt;
&lt;h3&gt;Style&lt;/h3&gt;
&lt;p&gt;This is a very informal book: something I have no problem with. English clearly isn&amp;#39;t the author&amp;#39;s first language, and although I don&amp;#39;t blame &lt;i&gt;him&lt;/i&gt; for some of the clumsy wording in the book (e.g. &amp;quot;We will not leave behind the necessary pragmatism in order to improve performance within a reasonable developing time&amp;quot;) I &lt;i&gt;do&lt;/i&gt; wish the book&amp;#39;s editorial team had done a better job in that respect. It&amp;#39;s tricky with technical books: non-technical editors have good reason to be wary of going too far, as small changes in wording can have make a large difference semantically, but it &lt;i&gt;does&lt;/i&gt; make a big difference to a book&amp;#39;s readability when the language is clear and idiomatic. (As a side note, I feel incredibly fortunate to have English as my native tongue. I&amp;#39;m not fluent in &lt;em&gt;any&lt;/em&gt;&amp;nbsp;foreign languages, and I&amp;#39;m often amazed at how well others manage.)&lt;/p&gt;
&lt;p&gt;There are other elements of the style of the book which I have much more of a problem with. The first is the way that the examples are handled. A very large proportion of the book is just lists of instructions: &amp;quot;add some using directives: &amp;lt;code&amp;gt;; add these variables: &amp;lt;code&amp;gt;; add this procedure: &amp;lt;code&amp;gt;; add another procedure &amp;lt;code&amp;gt;; add an event handler &amp;lt;code&amp;gt;&amp;quot; with just a sentence or two of explanation for each one as you go. There&amp;#39;s much more explanation after all the code has been added, but the &lt;i&gt;way&lt;/i&gt; that the code is given makes it very hard to see what&amp;#39;s going on. We almost never get to see a whole class in one listing - it&amp;#39;s always broken up into using directives, variables, individual methods etc. This may not be too bad if you&amp;#39;re following along with the book at every single point, but it makes it very hard to just &lt;i&gt;read.&lt;/i&gt; As a friend has commented, this content might work a lot better as a screencast, rather than as a book.&lt;/p&gt;
&lt;p&gt;One detailed gripe: nearly &lt;i&gt;every&lt;/i&gt; time a property is introduced, the author uses the phrase &amp;quot;we want to create a compact and reliable class&amp;quot; as a justification. There&amp;#39;s no explanation, and quite often the properties are mutable for no good reason (when a &lt;i&gt;genuinely&lt;/i&gt; reliable class in a multi-threaded setting would be immutable). After a while it made me want to grind my teeth every time I saw it.&lt;/p&gt;
&lt;p&gt;The feeling is very much that of a Head First book, but one which doesn&amp;#39;t work. For all my misgivings about Head First C# (which I believe is now very much better now that a large number of errors have been removed) the general style was very well handled. It&amp;#39;s not my preferred style to start with (particularly focusing on large GUIs instead of short, complete console apps) but I rarely felt particularly lost in the listings - there was usually enough context to hold onto. Here, I feel there&amp;#39;s very little context at all. If you accidentally miss out a step, you&amp;#39;ll have a really hard time working out which one it is or what&amp;#39;s wrong.&lt;/p&gt;
&lt;p&gt;On top of this, there&amp;#39;s the bizarre storyline &amp;quot;explaining&amp;quot; all the listings. Apparently you (the reader) originally started out cracking a code, then got hired by some other crackers, then the FBI, then NASA. We are told of FBI agents getting us capuccinos, the NASA CIO wanting you to use the Parallel Extensions CTP so that they can get free licences for Visual Studio 2010 and all kinds of other oddities. We are constantly bombarded with plaudits about our threading capabilities - by the last chapter we&amp;#39;re regularly being called &amp;quot;experts&amp;quot; and &amp;quot;threading gurus&amp;quot; despite the fact that we wouldn&amp;#39;t have a clue what was going on if someone presented us with some code using a &amp;quot;lock&amp;quot; statement. This is all patronising in the extreme - and again, Head First C# (and&amp;nbsp; I suspect the rest of the Head First series) handles the &amp;quot;keep it informal but drive the topic forward&amp;quot; aspect a lot more successfully.&lt;/p&gt;
&lt;p&gt;Finally, on the topic of style, I&amp;#39;d like to rant a bit about the coding style. It&amp;#39;s awful. Really awful. I realise that coding standards are to some extent a personal thing, but I object to code like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pseudo-Hungarian (the type which uses &amp;quot;o&amp;quot; as a prefix for almost any object; not the type Peter likes) &lt;i&gt;and&lt;/i&gt; the nature of every variable (local, parameter or instance variable) makes for horrendous variable names such as &amp;quot;prloOutputCharLabels&amp;quot;. It&amp;#39;s not even consistent - variables added by the designer only get a type designation prefix (lbl, but, pic) but no nature prefix. Aargh.  &lt;/li&gt;
&lt;li&gt;Methods are frequently camel-cased instead of Pascal-cased, e.g. &amp;quot;showFishes&amp;quot; and &amp;quot;checkCodeChar&amp;quot;. It&amp;#39;s possible that this is only true for private methods - a very &lt;i&gt;quick&lt;/i&gt; flick through doesn&amp;#39;t reveal any public ones like this - but if so it&amp;#39;s inconsistently applied as there are certainly Pascal-cased private methods too. Some public properties combine both annoyances so far, with names such as &amp;quot;poThread&amp;quot; and &amp;quot;piBegin&amp;quot;.  &lt;/li&gt;
&lt;li&gt;Most (but not all) of the time Hillar declares all of a method&amp;#39;s variables at the top, even if they&amp;#39;re not used for a long time. This includes declarations of variables for use in loops. This took me right back to the 80s, writing ANSI C again. I believe that the ability to declare variables at the point of first use gives a significant improvement in readability. It&amp;#39;s easier to see where a variable will be used if its scope is limited, for example.&lt;/li&gt;
&lt;li&gt;Using directives aren&amp;#39;t applied nearly thoroughly enough, leaving lots of explicit use of System.Diagnostics, System.Drawing, System.ComponentModel etc. Given the line length limitations in printed books, this is a real killer in terms of providing compact, readable code.  &lt;/li&gt;
&lt;li&gt;Speaking of line length limitations, it would be really useful to actually acknowledge them - if a comment is going to span two printed lines, starting just the first one with &amp;quot;//&amp;quot; and leaving the second indented but not really a comment isn&amp;#39;t a good idea. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, we&amp;#39;ve got code broken up into chunks which breaks the flow of the code, and I don&amp;#39;t even like the style of the code. Still, I could live with that if it&amp;#39;s good quality code...&lt;/p&gt;
&lt;h3&gt;Accuracy and best practices&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve already indicated one of the significant problems I have with the book in terms of content: its complete absence of discussion about shared data and locking. Yes, this is a beginner&amp;#39;s book, and I wasn&amp;#39;t expecting the level of detail on the memory model which is present in Joe Duffy&amp;#39;s book (which I promise I&amp;#39;ll review soon) - but I&amp;#39;d certainly prefer to err on the side of safety. The book regularly just accesses data on one thread having written it on another, with no locking, volatility or use of Interlocked. This isn&amp;#39;t the sole bad practice, however, and it&amp;#39;s not limited to stylistic choices either. In the course of the book, we are told all of the items below (and more). Italics indicate what the book claims; regular type indicates my response. These aren&amp;#39;t verbatim quotes, but paraphrase:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;i&gt;Forcing garbage collection before starting a multi-threaded operation is a good idea.&lt;/i&gt; This is given as a sort of response to a screenshot of Process Explorer showing ugly memory usage. In fact, I can&amp;#39;t reproduce the kind of nasty graph that&amp;#39;s shown in the book, even with the code downloaded from the web site, but if I &lt;i&gt;did&lt;/i&gt; see that there are definitely better ways of addressing it than forcing garbage collection. Disposing of Bitmaps appropriately would be a good start... as it is, each bitmap is going to hang around for at least one garbage collection cycle longer than it should, because we&amp;#39;ve got to wait for its finalizer to be executed. Making sure you dispose of objects appropriately is always a good idea - explicitly forcing the garbage collector is almost always a bad one. (Not absolutely always, but usually.)  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;WaitHandle.WaitAll has to run on an MTA thread - so let&amp;#39;s just change the [STAThread] line above the Main method to [MTAThread]&lt;/i&gt;, with no warning that it&amp;#39;s a really bad idea&lt;i&gt; &lt;/i&gt;to do that for Windows Forms. (Side-note: when trying to check that there really isn&amp;#39;t a warning, I had to spend a long time finding the section. The index doesn&amp;#39;t contain entries for MTAThread, STAThread, apartment, WaitHandle or WaitAll. In general the index could do with a lot of work. I&amp;#39;m painfully aware that indexing is a horrible task, but it&amp;#39;s important.)  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;Application.DoEvents() is a way of letting the UI process events.&lt;/i&gt; This is true - it&amp;#39;s also another &lt;i&gt;really bad idea&lt;/i&gt; unless you absolutely &lt;i&gt;have&lt;/i&gt; to use it. Re-entrancy is hard to debug - and not mentioned at all in the book, as far as I can tell.  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;Data streaming is wasteful, because two threads might both want to do I/O at the same time - it&amp;#39;s a better idea for each thread to load all the data it needs to and &lt;/i&gt;&lt;i&gt;then start processing it.&lt;/i&gt; This is stated in a context where streaming is ideal - each thread just needs to process every line in a file. (Each thread is asked to process a different file.) There&amp;#39;s no dependency between the lines of the file. It&amp;#39;s an absolute gift - the buffering and pre-fetch techniques of Windows would guess we needed the next block of data before we ask for it, so the disk would be seeking &lt;i&gt;while&lt;/i&gt; we&amp;#39;re encrypting, on each thread. At least, I strongly suspect it would - and I would &lt;i&gt;profile&lt;/i&gt; the thing instead of just claiming that we&amp;#39;ve managed to avoid an I/O bottleneck by loading files in their entirety up-front. No mention is made of the fact that as soon as a bunch of big files are queued for encryption, you&amp;#39;ll have a bunch of threads all trying to load everything before they bother starting to do anything. Avoiding I/O contention is a tricky topic, and it deserves better than a couple of misleading paragraphs with no attempt at explaining what the benefits of streaming the data would be.  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;The thread pool is used to queue threads with work to do. If there are already lots of threads busy, the new threads will wait until the old ones have finished.&lt;/i&gt; Note the use of &amp;quot;threads&amp;quot; here - not &lt;i&gt;tasks&lt;/i&gt; to run on a pool of existing threads, but &lt;i&gt;threads&lt;/i&gt;. This would make the thread pool pointless - what is never explained in the book is that creating threads is a relatively expensive business, and you don&amp;#39;t want to do it repeatedly for short-lived tasks when you could instead create a pool of threads and reuse them to run several tasks. Once this purpose is clear, the notion of queuing &lt;i&gt;threads&lt;/i&gt; becomes obviously wrong.  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;We can pass some state into the delegate used for work item queuing (or a ParameterizedThreadStart) and use that to give us some context. We need to cast that state to the relevant type before we can use it, because it&amp;#39;s just typed as System.Object.&lt;/i&gt; So far so good - except most of the time, Hillar ends up passing into the work item the same reference which would be available as just &amp;quot;this&amp;quot; within the method itself. So we have code such as: &lt;/li&gt;
&lt;/ul&gt;
&lt;div class="code"&gt;loPiece.poThread = &lt;span class="Keyword"&gt;new&lt;/span&gt; Thread(&lt;span class="Keyword"&gt;new&lt;/span&gt; ParameterizedThreadStart(loPiece.ThreadMethod)); &lt;br /&gt;... &lt;br /&gt;loPiece.poThread.Start(loPiece); &lt;/div&gt;
&lt;ul&gt;
&lt;li style="list-style-type:none;"&gt;The ThreadMethod method then duly casts its parameter to its own type and uses it. All of this is pointless, as the method doesn&amp;#39;t need any parameters - it can just use &amp;quot;this&amp;quot; inside the method.  &lt;/li&gt;
&lt;li&gt;&lt;i&gt;It&amp;#39;s very important to initialize lists with the right capacity.&lt;/i&gt; Again, this isn&amp;#39;t too bad as far as it goes - except that this micro-optimisation goes awry when he reads the TextBox.Lines property twice: once to work out the appropriate capacity and once to fill the list with initial data. Unfortunately the TextBox.Lines property has to take the existing text in the TextBox, split it (creating a bunch of substrings) and get the result into an array. This in turn means doing all the normal shenanigans associated with creating buffers which are bigger than you need, filling them, copying to a new buffer etc - exactly what we&amp;#39;re trying to avoid! This &amp;quot;optimization&amp;quot; will usually &lt;em&gt;cost&lt;/em&gt;&amp;nbsp;time instead of saving it. It could be easily fixed by just fetching the array in one statement, then using the same array for both the count and the list population. In fact, if you just pass the array into the List&amp;lt;T&amp;gt; constructor, it will perform the optimization for you - it can detect that it&amp;#39;s an ICollection&amp;lt;T&amp;gt; and use the Count property directly. Writing the &lt;em&gt;simplest&lt;/em&gt;&amp;nbsp;code actually ends up being optimal.&lt;/li&gt;
&lt;li&gt;The above bullet point isn&amp;#39;t going to dominate the performance of that example though - there&amp;#39;s a potentially far worse effect due to the way the resulting &amp;quot;encrypted&amp;quot; string is broken up each time: using string concatenation in a loop. I guess we&amp;#39;d better hope there are no really long lines. If an author is going to give optimisation &amp;quot;tips&amp;quot; they need to be a &lt;i&gt;lot&lt;/i&gt; more rigorous than this. Using string concatenation in a loop is probably the single best-known performance no-no in .NET. I was really shocked to see this in a book which is supposedly about making your code perform &lt;i&gt;better&lt;/i&gt;. Now, it could be that string concatenation was used deliberately to slow things down - but in that case, why not highlight it? Drawing attention to intended optimizations gives the impression that the rest of the code is either optimized or has at least been written reasonably. If &amp;quot;bad&amp;quot; code is to be used for a specific purpose, that should be called out so that the reader won&amp;#39;t go onto use the same kind of code in their own production apps (which really &lt;em&gt;shouldn&amp;#39;t&lt;/em&gt;&amp;nbsp;be deliberately slowed down).&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="list-style-type:none;"&gt;These aren&amp;#39;t the only issues I have with the code. Unicode is abused by &amp;quot;encrypting&amp;quot; text with no discussion of whether the strings he produces are valid or not (as opposed to the normal practice of only encrypting data after first converting it into binary; the encrypted binary might then be converted to text using base64 if you need to transmit the encrypted data as text). We could easily end up with strings containing surrogate high or low code points without the corresponding half in the appropriate place. When analyzing a bitmap he uses GetPixel and SetPixel for each pixel, rather than calling LockBits once and then accessing the image data in a much faster manner. (The code given does scale, but it&amp;#39;s not as fast as it could be. Using LockBits it would still scale, but the &amp;quot;per thread&amp;quot; work would be faster.) There are other, similar issues lurking in the text, but I&amp;#39;m sure you get the gist of the problem.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Believe it or not, there are things about this book that I actually &lt;i&gt;like&lt;/i&gt;. It&amp;#39;s relatively thin, which has very tangible advantages when you&amp;#39;re carrying it around a lot. The sections explaining has to use Process Explorer and Task Manager to their best are useful, and the ideas of the examples are good - even though they basically cover the same ground several times. Unfortunately the bad points outweight the good far too heavily. To summarise them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What I consider some of the absolute &lt;i&gt;core&lt;/i&gt; elements of .NET multithreading (locking and monitors in particular) aren&amp;#39;t covered at all  &lt;/li&gt;
&lt;li&gt;Only the simple situation of an embarrassingly parallel algorithm is covered. In the real world developers will have to face &lt;i&gt;real&lt;/i&gt; challenges where tasks don&amp;#39;t always split themselves up nicely into totally independent chunks. A reader who finishes this book assured that they are now threading gurus will face a nasty shock.  &lt;/li&gt;
&lt;li&gt;Server-side threading isn&amp;#39;t given much coverage at all, despite this being arguably the most likely environment for developers to encounter multithreading  &lt;/li&gt;
&lt;li&gt;The &amp;quot;story&amp;quot; element of the prose style is childish and patronising  &lt;/li&gt;
&lt;li&gt;The coding style, while a personal choice, makes me wince - and is particularly verbose for a book, where space is important  &lt;/li&gt;
&lt;li&gt;Many bad practices are encouraged, and there are plenty of important misunderstandings to trip up readers  &lt;/li&gt;
&lt;li&gt;The index has failed me (even when I&amp;#39;ve known that the subject is in the book) more times than it&amp;#39;s helped me &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#39;s a real pity. I was hoping this would be a book I could recommend to people as a precursor to reading Joe Duffy&amp;#39;s excellent &lt;a href="http://www.amazon.com/products/dp/032143482X"&gt;Concurrent Programming on Windows&lt;/a&gt;. Instead, my current best advice is to read &lt;a href="http://www.albahari.com/threading/default.aspx"&gt;Joe Albahari&amp;#39;s threading tutorial&lt;/a&gt;. (I previously had a link to my own threading tutorial as well, but apparently this made people think I was fishing for more readers of that.)&amp;nbsp;I&amp;#39;m sure there are good introductory threading books out there, but I&amp;#39;m afraid this isn&amp;#39;t one of them.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1678753" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Book+reviews/default.aspx">Book reviews</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>DotNetRocks interview</title><link>http://msmvps.com/blogs/jon_skeet/archive/2008/10/07/dotnetrocks-interview.aspx</link><pubDate>Tue, 07 Oct 2008 16:33:49 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1650011</guid><dc:creator>skeet</dc:creator><slash:comments>5</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1650011</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1650011</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2008/10/07/dotnetrocks-interview.aspx#comments</comments><description>&lt;p&gt;Last Monday evening I had a chat with the guys from &lt;a href="http://dotnetrocks.com"&gt;DotNetRocks&lt;/a&gt;, and today &lt;a href="http://www.dotnetrocks.com/default.aspx?showNum=383"&gt;the show has gone live&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I wouldn&amp;#39;t claim to have said anything &lt;em&gt;particularly&lt;/em&gt; earth-shattering, and regular readers will probably be familiar with many of the themes anyway, but I thoroughly enjoyed it and hope you will too. Amongst other things, we talked about:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Protocol buffers&lt;/li&gt; &lt;li&gt;Implicit typing and anonymous types&lt;/li&gt; &lt;li&gt;Why it doesn&amp;#39;t bother me that Office hasn&amp;#39;t been ported to .NET&lt;/li&gt; &lt;li&gt;C# 4&lt;/li&gt; &lt;li&gt;My wishlist for C#&lt;/li&gt; &lt;li&gt;Threading and Parallel Extensions&lt;/li&gt; &lt;li&gt;Working for Google&lt;/li&gt; &lt;li&gt;How to learn LINQ&lt;/li&gt; &lt;li&gt;C# in Depth&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Feedback welcome. And yes, I know I sound somewhat like a stereotypical upper-class idiot at times. Unfortunately there&amp;#39;s not a lot I can do about that. Only the &amp;quot;idiot&amp;quot; part is accurate :)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1650011" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/General/default.aspx">General</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_+4/default.aspx">C# 4</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Google/default.aspx">Google</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Protocol+Buffers/default.aspx">Protocol Buffers</category></item><item><title>Parallel Extensions June CTP</title><link>http://msmvps.com/blogs/jon_skeet/archive/2008/06/02/parallel-extensions-june-ctp.aspx</link><pubDate>Mon, 02 Jun 2008 22:56:46 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1630450</guid><dc:creator>skeet</dc:creator><slash:comments>5</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1630450</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1630450</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2008/06/02/parallel-extensions-june-ctp.aspx#comments</comments><description>&lt;p&gt;Either my timing is great or it&amp;#39;s lousy - you decide. Yesterday I posted about parallelising Conway&amp;#39;s Life - and today the &lt;a href="http://blogs.msdn.com/pfxteam/archive/2008/06/02/8567093.aspx"&gt;new CTP for the Parallel Extensions library&lt;/a&gt; comes out! The bad news is that it meant I had to run all the tests again... the good news is that it means we can see whether or not the team&amp;#39;s work over the last 6 months has paid off.&lt;/p&gt; &lt;h4&gt;&lt;/h4&gt; &lt;h3&gt;Breaking change&lt;/h3&gt; &lt;p&gt;The only breaking change I&amp;#39;ve seen is that &lt;code&gt;AsParallel()&lt;/code&gt; no longer takes a &lt;code&gt;ParallelQueryOptions&lt;/code&gt; parameter - instead, you call &lt;code&gt;AsOrdered()&lt;/code&gt; on the value returned from &lt;code&gt;AsParallel()&lt;/code&gt;. It was an easy change to make, and worked first time. There may well be plenty of other breaking API changes which are more significant, of course - I&amp;#39;m hardly using any of it.&lt;/p&gt; &lt;h3&gt;Benchmark comparisons&lt;/h3&gt; &lt;p&gt;One of the nice things about having the previous blog entries is that I can easily compare the results of how things were then with how they are now. Here are the test results for the areas of the previous blog posts which used Parallel Extensions. For the Game of Life, I haven&amp;#39;t included the results with the rendering for the fast version, as they&amp;#39;re still bound by rendering (unsurprisingly).&lt;/p&gt; &lt;h4&gt;Mandelbrot set&lt;/h4&gt; &lt;p&gt;&lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2008/05/18/mandelbrot-revisited-benchmark-edition.aspx"&gt;(Original post)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Results are in milliseconds taken to plot the whole image, so less is better. (x;y) values mean &amp;quot;Width=x, MaxIterations=y.&amp;quot;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt; &lt;th&gt;Description&lt;/th&gt; &lt;th&gt;December CTP&lt;/th&gt; &lt;th&gt;June CTP&lt;/th&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelForLoop (1200;200)&lt;/td&gt; &lt;td&gt;376&lt;/td&gt; &lt;td&gt;380&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelForLoop (3000;200)&lt;/td&gt; &lt;td&gt;2361&lt;/td&gt; &lt;td&gt;2394&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelForLoop (1200;800)&lt;/td&gt; &lt;td&gt;1292&lt;/td&gt; &lt;td&gt;1297&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowInPlace (1200;200)&lt;/td&gt; &lt;td&gt;378&lt;/td&gt; &lt;td&gt;393&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowInPlace (3000;200)&lt;/td&gt; &lt;td&gt;2347&lt;/td&gt; &lt;td&gt;2440&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowInPlace (1200;800)&lt;/td&gt; &lt;td&gt;1295&lt;/td&gt; &lt;td&gt;1939&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowWithCopy (1200;200)&lt;/td&gt; &lt;td&gt;382&lt;/td&gt; &lt;td&gt;411&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowWithCopy (3000;200)&lt;/td&gt; &lt;td&gt;2376&lt;/td&gt; &lt;td&gt;2484&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowWithCopy (1200;800)&lt;/td&gt; &lt;td&gt;1288&lt;/td&gt; &lt;td&gt;1401&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithGenerator (1200;200)&lt;/td&gt; &lt;td&gt;4782&lt;/td&gt; &lt;td&gt;4868&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithGenerator (3000;200)&lt;/td&gt; &lt;td&gt;29752&lt;/td&gt; &lt;td&gt;31366&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithGenerator (1200;800)&lt;/td&gt; &lt;td&gt;16626&lt;/td&gt; &lt;td&gt;16855&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithSequenceOfPoints (1200;200)&lt;/td&gt; &lt;td&gt;549&lt;/td&gt; &lt;td&gt;533&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithSequenceOfPoints (3000;200)&lt;/td&gt; &lt;td&gt;3413&lt;/td&gt; &lt;td&gt;3290&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithSequenceOfPoints (1200;800)&lt;/td&gt; &lt;td&gt;1462&lt;/td&gt; &lt;td&gt;1460&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalleLinqInPlace (1200;200)&lt;/td&gt; &lt;td&gt;422&lt;/td&gt; &lt;td&gt;440&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalleLinqInPlace (3000;200)&lt;/td&gt; &lt;td&gt;2586&lt;/td&gt; &lt;td&gt;2775&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalleLinqInPlace (1200;800)&lt;/td&gt; &lt;td&gt;1317&lt;/td&gt; &lt;td&gt;1475&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithDelegate (1200;200)&lt;/td&gt; &lt;td&gt;509&lt;/td&gt; &lt;td&gt;514&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithDelegate (3000;200)&lt;/td&gt; &lt;td&gt;3093&lt;/td&gt; &lt;td&gt;3134&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithDelegate (1200;800)&lt;/td&gt; &lt;td&gt;1392&lt;/td&gt; &lt;td&gt;1571&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithGenerator (1200;200)&lt;/td&gt; &lt;td&gt;5046&lt;/td&gt; &lt;td&gt;5511&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithGenerator (3000;200)&lt;/td&gt; &lt;td&gt;31657&lt;/td&gt; &lt;td&gt;30258&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithGenerator (1200;800)&lt;/td&gt; &lt;td&gt;17026&lt;/td&gt; &lt;td&gt;19517&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqSimple (1200;200)&lt;/td&gt; &lt;td&gt;556&lt;/td&gt; &lt;td&gt;595&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqSimple (3000;200)&lt;/td&gt; &lt;td&gt;3449&lt;/td&gt; &lt;td&gt;3700&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqSimple (1200;800)&lt;/td&gt; &lt;td&gt;1448&lt;/td&gt; &lt;td&gt;1506&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalelLinqWithStruct (1200;200)&lt;/td&gt; &lt;td&gt;511&lt;/td&gt; &lt;td&gt;534&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalelLinqWithStruct (3000;200)&lt;/td&gt; &lt;td&gt;3227&lt;/td&gt; &lt;td&gt;3154&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalelLinqWithStruct (1200;800)&lt;/td&gt; &lt;td&gt;1427&lt;/td&gt; &lt;td&gt;1445&lt;/td&gt;&lt;/tr&gt; &lt;/table&gt;  &lt;p&gt;A mixed bag, but overall it looks to me like the June CTP was slightly worse than the older one. Of course, that&amp;#39;s assuming that everything else on my computer is the same as it was a couple of weeks ago, etc. I&amp;#39;m not going to claim it&amp;#39;s a perfect benchmark by any means. Anyway, can it do any better with the Game of Life?&lt;/p&gt;  &lt;h4&gt;Game of Life&lt;/h4&gt; &lt;p&gt;&lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2008/06/01/more-parallelisation-fun-conway-s-game-of-life.aspx"&gt;(Original post)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Results are in frames per second, so more is better.&lt;/p&gt; &lt;table&gt;  &lt;tr&gt; &lt;th&gt;Description&lt;/th&gt; &lt;th&gt;December CTP&lt;/th&gt; &lt;th&gt;June CTP&lt;/th&gt; &lt;tr&gt; &lt;td&gt;ParallelFastInteriorBoard (rendered)&lt;/td&gt; &lt;td&gt;23&lt;/td&gt; &lt;td&gt;22&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelFastInteriorBoard (unrendered)&lt;/td&gt; &lt;td&gt;29&lt;/td&gt; &lt;td&gt;28&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelFastInteriorBoard&lt;/td&gt; &lt;td&gt;508&lt;/td&gt; &lt;td&gt;592&lt;/td&gt;&lt;/tr&gt; &lt;/table&gt; &lt;p&gt; Yes, ParallelFastInteriorBoard really did get that much speed bump, apparently. I have no idea why... which leads me to the slightly disturbing conclusion of this post: &lt;/p&gt; &lt;h3&gt;Conclusion&lt;/h3&gt; &lt;p&gt; The numbers above don&amp;#39;t mean much. At least, they&amp;#39;re not particularly useful because I don&amp;#39;t understand them. Why would a few tests become &amp;quot;slightly significantly worse&amp;quot; and one particular test get markedly better? Do I have serious benchmarking issues in terms of my test rig? (I&amp;#39;m sure it could be a lot &amp;quot;cleaner&amp;quot; - I didn&amp;#39;t think it would make very much difference.) &lt;/p&gt; &lt;p&gt; I&amp;#39;ve always been aware that off-the-cuff benchmarking like this was of slightly dubious value, at least in terms of the details. The only facts which are really useful here are the big jumps due to algorithm etc. The rest is noise which may interest Joe and the team (and hey, if it proves to be useful, that&amp;#39;s great) but which is beyond my ability to reason about. Modern machines are complicated beasts, with complicated operating systems and complicated platforms above them. &lt;/p&gt; &lt;p&gt;So ignore the numbers. Maybe the new CTP is faster for your app, maybe it&amp;#39;s not. It &lt;i&gt;is&lt;/i&gt; important to know it&amp;#39;s out there, and that if you&amp;#39;re interested in parallelisation but haven&amp;#39;t played with it yet, this is a good time to pick it up. &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1630450" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>More parallelisation fun: Conway's Game of Life</title><link>http://msmvps.com/blogs/jon_skeet/archive/2008/06/01/more-parallelisation-fun-conway-s-game-of-life.aspx</link><pubDate>Sun, 01 Jun 2008 19:22:32 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1629981</guid><dc:creator>skeet</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1629981</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1629981</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2008/06/01/more-parallelisation-fun-conway-s-game-of-life.aspx#comments</comments><description>&lt;p&gt;Okay, so I&amp;#39;ve probably exhausted the Mandelbrot set as a source of interest, at least for the moment. However, every so often someone mentions something or other which sounds like a fun exercise in parallelisation. The latest one is &lt;a href="http://en.wikipedia.org/wiki/Conway&amp;#39;s_Game_of_Life"&gt;Conway&amp;#39;s Game of Life&lt;/a&gt;. Like the Mandelbrot set, this is something I used to play with when I was a teenager - and like the Mandelbrot set, it&amp;#39;s an &lt;a href="http://en.wikipedia.org/wiki/Embarrassingly_parallel"&gt;embarrassingly parallel&lt;/a&gt; problem.&lt;/p&gt; &lt;p&gt;As before, I&amp;#39;ve written a few implementations but not worked &lt;em&gt;excessively&lt;/em&gt; hard to optimise. All my tests were performed with a game board of 1000x500 cells, displaying at one pixel per cell (partly because that was the easiest way to draw it). I found that with the slower implementations the rendering side was irrelevant to the results, but the rendering became a bottleneck with the fast algorithms, so I&amp;#39;ve included results both with and without rendering.&lt;/p&gt; &lt;p&gt;The &amp;quot;benchmark&amp;quot; code (I use the word very lightly - normal caveats around methodology apply, this was only a weekend evenings project after all) records the &amp;quot;current&amp;quot; number of frames per second roughly once per second, and also an average over the whole run. (The fast algorithm jump around in fps pretty wildly depending on what&amp;#39;s going on - the more naïve&lt;strong&gt; &lt;/strong&gt;ones don&amp;#39;t really change much between a busy board and a dead one.) The starting pattern in all cases was the R-pentomino (or F-pentomino, depending on whose terminology you use) in the middle of the board.&lt;/p&gt; &lt;p&gt;A base class (&lt;code&gt;BytePerPixelBoard&lt;/code&gt;) is used by all the implementations - this represents the board with a single byte per pixel, which is really easy to work with but obviously very wasteful in terms of memory (and therefore cache). I haven&amp;#39;t investigated any other representations, but I wanted to get this post out fairly quickly as I have a lot of other stuff to do at the moment. (I also have a post on Java closures which I should tidy up soon, but hey...)&lt;/p&gt; &lt;h4&gt;Implementation smells&lt;/h4&gt; &lt;p&gt;All the source code is &lt;a href="http://pobox.com/~skeet/csharp/blogfiles/GameOfLife.zip"&gt;available for download&lt;/a&gt;, of course. There are no fancy options for verifying the results or even turning off rendering - just comment out the line in &lt;code&gt;Program.BackgroundLoop&lt;/code&gt; which calls &lt;code&gt;Render&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;It would be fairly easy to make ParallelFastInteriorBoard derive from SimpleFastInteriorBoard, and ParallelActiveBlockBoard derive from ActiveBlockBoard, but I&amp;#39;ve kept the implementations completely separate for the moment. That means a huge amount of duplication, of course, including a nested class in each of the &amp;quot;active block&amp;quot; implementations - the nested classes are exactly the same. Oh, and they use internal fields instead of properties. I wanted the fields to be read-only, so I couldn&amp;#39;t use automatic properties - but I didn&amp;#39;t want to go to the hassle of having explicitly declared fields and separate properties. Trust me, &lt;a href="http://csharpindepth.com/Articles/Chapter8/PropertiesMatter.aspx"&gt;I wouldn&amp;#39;t do this in production code&lt;/a&gt;. (There were some other internal fields, but they&amp;#39;ve mercifully been converted into properties now.)&lt;/p&gt; &lt;h4&gt;SimpleBoard&lt;/h4&gt; &lt;p&gt;This is about as simple as you can get. It fetches each cell&amp;#39;s current value &amp;quot;carefully&amp;quot; (i.e. coping with wrapping) regardless of where it is on the board. It works, but it&amp;#39;s almost painfully slow.&lt;/p&gt; &lt;h4&gt;SimpleFastInteriorBoard&lt;/h4&gt; &lt;p&gt;This is just an optimisation of SimpleBoard. We fetch the borders of the board &amp;quot;slowly&amp;quot; as per SimpleBoard, but once we&amp;#39;re on the interior of the board (any cell that&amp;#39;s not on an edge) we know we won&amp;#39;t need to worry about wrapping, so we can just use a fixed set of array offsets relative to the offset of the cell that&amp;#39;s being calculated.&lt;/p&gt; &lt;p&gt;This ends up being significantly faster, although it could still be optimised further. In the current code each cell is tested for whether it&amp;#39;s an interior cell or not. There&amp;#39;s a slight optimisation by remembering the results for &amp;quot;am I looking at the top or the bottom row&amp;quot; for a whole row, but by calculating just the edges and then the interior (or vice versa) a lot of tests could be avoided.&lt;/p&gt; &lt;h4&gt;ParallelFastInteriorBoard&lt;/h4&gt; &lt;p&gt;Behold the awesome power of &lt;code&gt;Parallel.For&lt;/code&gt;. Parallelising SimpleFastInteriorBoard literally took about a minute. I haven&amp;#39;t seen any toolkit other rival this in terms of ease of use. Admittedly I haven&amp;#39;t done much embarrassingly parallel work in many other toolkits, but even so it&amp;#39;s impressive. It&amp;#39;ll be interesting to see how easy it is to use for complex problems as well as simple ones.&lt;/p&gt; &lt;p&gt;If you look at the results, you&amp;#39;ll see this is the first point at which there&amp;#39;s a difference between rendering and not. As the speed of the calculation improves the constant time taken for rendering becomes more significant.  &lt;h4&gt;ActiveBlockBoard&lt;/h4&gt; &lt;p&gt;This is a departure in terms of implementation. We still use the same backing store (one big array of a byte per cell) but the board is logically broken up into blocks of 25x25 cells. (The size is hard-coded, but only as two constants - they can easily be changed. I haven&amp;#39;t investigated the effect of different block sizes thoroughly, but a few ad hoc tests suggests this is a reasonable sweet spot.) When considering a block in generation n, the changes (if any) in generation n-1 are considered. If the block itself changed, it may change again. If the relevant borders of any of its neighbours changed (including corners of diagonally neighbouring blocks), the cell may change. No other changes in the board can affect the block, so if none of these conditions are met the contents of generation n-1 can be copied to generation n for that block. This is obviously a massive saving when there are large areas of stable board, either dead or with stable patterns (such as 2x2 live blocks). It doesn&amp;#39;t help with blinkers (3x1 blocks which oscillate between vertical and horizontal alignments) or anything similar, but it&amp;#39;s still a big optimisation. It does mean a bit more house-keeping in terms of remembering what changed (anywhere in the cell, the four sides, and the four corners) each generation, but that pales into insignificance when you can remove the work from a lot of blocks.&lt;/p&gt; &lt;p&gt;Again, my implementation is relatively simple - but it still took a lot of work to get right! The code is &lt;em&gt;much&lt;/em&gt; more complicated than SimpleFastInteriorBoard, and indeed pretty much all the code from SFIB is used in ActiveBlockBoard. Again, I&amp;#39;m sure there are optimisations that could be made to it, and alternative strategies for implementing it. For instance, I did toy with some code which didn&amp;#39;t keep track of what had changed in the block, but instead pushed changes to relevant neighbouring blocks, explicitly saying, &amp;quot;You need to recalculate next time!&amp;quot; There wasn&amp;#39;t much difference in speed, however, and it needed an extra setup stage in order to make the code understandable, so I&amp;#39;ve removed it.&lt;/p&gt; &lt;p&gt;The performance improvement of this is staggering - it makes it over 20 times faster than the single-threaded SimpleFastInteriorBoard. At first the improvement was much smaller - until I realised that actually I&amp;#39;d moved the bottleneck to the rendering, and re-benchmarked with rendering turned off.&lt;/p&gt; &lt;h4&gt;ParallelActiveBlockBoard&lt;/h4&gt; &lt;p&gt;Exactly the same implementation as ActiveBlockBoard, but using a &lt;code&gt;Parallel.ForEach&lt;/code&gt; loop. Again, blissfully easy to implement, and very effective. It didn&amp;#39;t have the same near-doubling effect of SimpleFastInteriorBoard to ParallelFastInteriorBoard (with rendering turned off) but I suspect this is because the amount of housekeeping work required to parallelise things starts becoming more important at the rates shown in the results. It&amp;#39;s still impressive stuff though.&lt;/p&gt; &lt;h3&gt;Results&lt;/h3&gt; &lt;table&gt;  &lt;tr&gt; &lt;th&gt;Implementation&lt;/th&gt; &lt;th&gt;FPS with rendering&lt;/th&gt; &lt;th&gt;FPS without rendering&lt;/th&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SimpleBoard&lt;/td&gt; &lt;td&gt;4&lt;/td&gt; &lt;td&gt;4&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SimpleFastInteriorBoard&lt;/td&gt; &lt;td&gt;15&lt;/td&gt; &lt;td&gt;15&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelFastInteriorBoard&lt;/td&gt; &lt;td&gt;23&lt;/td&gt; &lt;td&gt;29&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ActiveBlockBoard&lt;/td&gt; &lt;td&gt;78&lt;/td&gt; &lt;td&gt;326&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelActiveBlockBoard&lt;/td&gt; &lt;td&gt;72&lt;/td&gt; &lt;td&gt;508&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;Conclusions&lt;/h3&gt; &lt;ul&gt; &lt;li&gt;The Parallel Extensions library rocks my world. Massive, massive kudos to Joe Duffy and the team.  &lt;li&gt;I have no idea why ParallelActiveBlockBoard is &lt;i&gt;slower&lt;/i&gt; than ActiveBlockBoard when rendering is enabled. This was repeatable (with slightly different results each time, but the same trend).  &lt;li&gt;Always be aware that reducing one bottleneck may mean something else becomes your next bottleneck - at first I thought that ActiveBlockBoard was &amp;quot;only&amp;quot; 5 times faster than SimpleFastInteriorBoard; it was only when parallelisation showed no improvement (just the opposite) that I started turning rendering off. &lt;li&gt;Changing the approach can have more pronounced effects than adding cores - moving to the &amp;quot;active block&amp;quot; model gave a &lt;em&gt;massive&lt;/em&gt; improvement over brute force. &lt;li&gt;Micro-optimisation has its place, when highly targeted and backed up with data - arguably the &amp;quot;fast interior&amp;quot; is a micro-optimisation, but again the improvement is quite dramatic. &lt;li&gt;Benchmarks are fun, but shouldn&amp;#39;t be taken to be indicative of anything else.  &lt;li&gt;Did I mention that Parallel Extensions rocks? &lt;/li&gt;&lt;/ul&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1629981" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>Mandelbrot revisited - benchmark edition</title><link>http://msmvps.com/blogs/jon_skeet/archive/2008/05/18/mandelbrot-revisited-benchmark-edition.aspx</link><pubDate>Sun, 18 May 2008 21:44:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1624136</guid><dc:creator>skeet</dc:creator><slash:comments>11</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1624136</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1624136</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2008/05/18/mandelbrot-revisited-benchmark-edition.aspx#comments</comments><description>&lt;p&gt;I&amp;#39;ve had fun with the Mandelbrot set in this blog before, using it as an example of an embarrassingly parallelisable problem and demonstrating Parallel LINQ with it. &lt;/p&gt; &lt;p&gt;This morning, over breakfast, I described the problem to &lt;a href="http://www.brunschen.com/christian/"&gt;Christian Brunschen&lt;/a&gt;, a colleague of mine who has some parallelisation experience through &lt;a href="http://www.brunschen.com/christian/OdinMP/"&gt;implementing OpenMP in a portable manner&lt;/a&gt;. He immediately suggested a few possible changes to the way that I&amp;#39;ve approached things. Given the number of different attempts I&amp;#39;ve now had, I thought it would make sense to write a litte benchmarking app which could easily be expanded as further implementations were considered. The benchmark is &lt;a href="http://pobox.com/%7Eskeet/csharp/blogfiles/MandelbrotBenchmark.zip"&gt;available for download&lt;/a&gt; so you can play around with it yourself. (As is often the case with this kind of thing, it&amp;#39;s not the nicest code in the world for various reasons - the duplication of the sequence generation method, for example... Please don&amp;#39;t use it as a tutorial on actual coding and organisation.)&lt;/p&gt; &lt;h3&gt;Benchmark implementation details&lt;/h3&gt; &lt;p&gt;The benchmark allows you to vary the size of the generated image and the maximum number of iterations per point. The images can be displayed after the test run, but only the time taken to populate a byte array is recorded. The byte arrays are all allocated before any tests are run, and the garbage collector is invoked (as far as it can be) between tests. The images themselves are only generated after the tests have all completed. Each implementation is given a tiny &amp;quot;dummy run&amp;quot; (creating an image 10 pixels across, with a maximum of 2 iterations per point) first to hopefully remove JITting from the benchmarking times. I won&amp;#39;t pretend that this puts everything on a completely level playing field (benchmarking is hard) but hopefully it&amp;#39;s a good start. We check the similarity between the results of each test and the first one - in some cases they could be &amp;quot;nearly all the same&amp;quot; without there being a bug, due to the subtleties of floating point operations and inlining.&lt;/p&gt; &lt;p&gt;The base class that all the generators use takes care of working out the height from the width, remembering the various configuration options, allocating the array, and also providing a simple imperative method to obtain the value of a single point, without using anything fancy. &lt;/p&gt; &lt;p&gt;I originally wanted to put the whole thing in a nice UI, but after wasting nearly an hour trying to get WPF to behave, I decided to go for a simple console app. One day I really must learn how to write GUIs quickly... &lt;/p&gt; &lt;p&gt;Okay, enough introduction - let&amp;#39;s look at the implementations we&amp;#39;re testing, and then the results. The idea is to get a look at how a number of areas influence the results, as well as seeing some nifty ways of expressing things functionally.&lt;/p&gt; &lt;h4&gt;SingleThreadImperativeSimple&lt;/h4&gt; &lt;p&gt;This is the most trivial code you could write, basically. As the base class provides the calculation method, we just go through each row and column, work out the value, and store it in the array. The only attempt at optimisation is keeping the &amp;quot;current index&amp;quot; into the array rather than calculating it for every point.&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; row = 0; row &amp;lt; Height; row++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;where &lt;code&gt;ComplexMandelbrotIndex&lt;/code&gt; is in the base class:&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;protected&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt; ComputeMandelbrotIndex(&lt;span class="ValueType"&gt;int&lt;/span&gt; row, &lt;span class="ValueType"&gt;int&lt;/span&gt; col)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; x = (col * SampleWidth) / Width + OffsetX;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; y = (row * SampleHeight) / Height + OffsetY;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; y0 = y;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; x0 = x;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; i = 0; i &amp;lt; MaxIterations; i++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;if&lt;/span&gt; (x * x + y * y &amp;gt;= 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)((i % 255) + 1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; xtemp = x * x - y * y + x0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; y = 2 * x * y + y0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; x = xtemp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; 0;&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;SingleThreadImperativeInline&lt;/h4&gt; &lt;p&gt;This is largely the same code as SingleThreadImperativeSimple, but with everything inlined. Within the main body, there&amp;#39;s no access to anything other than local variables, and no method calls. One further optimisation is available: points which will have a value of 0 aren&amp;#39;t stored (we assume we&amp;#39;ll always start with a cleared array). &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; width = Width;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; height = Height;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; maxIterations = MaxIterations;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;byte&lt;/span&gt;[] data = Data;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; row = 0; row &amp;lt; height; row++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; x = (col * SampleWidth) / width + OffsetX;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; y = (row * SampleHeight) / height + OffsetY;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; y0 = y;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; x0 = x;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; i = 0; i &amp;lt; maxIterations; i++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;if&lt;/span&gt; (x * x + y * y &amp;gt;= 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; data[index] = (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)((i % 255) + 1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;break&lt;/span&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; xtemp = x * x - y * y + x0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; y = 2 * x * y + y0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; x = xtemp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Leave data[index] = 0 by default&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; index++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;MultiThreadUpFrontSplitImperative&lt;/h4&gt; &lt;p&gt;This should be pretty efficient - work out how many cores we&amp;#39;ve got, split the work equally between them (as chunks of rows, which helps in terms of locality and just incrementing an index in the byte array each time), and then run. Wait until all the threads have finished, and we&amp;#39;re done. Shouldn&amp;#39;t be a lot of context switching required normally, and no synchronization is required. However, if some cores are busy with another process, we&amp;#39;ll end up context switching for no gain. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; cores = Environment.ProcessorCount;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; rowsPerCore = Height / cores;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; List&amp;lt;Thread&amp;gt; threads = &lt;span class="Keyword"&gt;new&lt;/span&gt; List&amp;lt;Thread&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; i = 0; i &amp;lt; cores; i++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; firstRow = rowsPerCore * i;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; rowsToCompute = rowsPerCore;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;if&lt;/span&gt; (i == cores - 1)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rowsToCompute = Height-(rowsPerCore*(cores-1));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Thread thread = &lt;span class="Keyword"&gt;new&lt;/span&gt; Thread(() =&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = firstRow * Width;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; row = firstRow; row &amp;lt; firstRow + rowsToCompute; row++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; });&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; thread.Start();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; threads.Add(thread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; threads.ForEach(thread =&amp;gt; thread.Join());&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;MultiThreadRowFetching&lt;/h4&gt; &lt;p&gt;Again, we use a fixed number of threads - but this time we start off with a queue of work - one task per row. The queue has to be synchronized every time we use it, and we also have to check whether or not it&amp;#39;s empty. Plenty of scope for lock contention here, particularly as the number of cores increases. &lt;/p&gt; 
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;
&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt;&amp;nbsp;Generate()&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Queue&amp;lt;&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;gt;&amp;nbsp;rowsLeft&amp;nbsp;=&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;Queue&amp;lt;&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;gt;(Enumerable.Range(0,&amp;nbsp;Height));&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Thread&amp;gt;&amp;nbsp;threads&amp;nbsp;=&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;List&amp;lt;Thread&amp;gt;();&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;Environment.ProcessorCount;&amp;nbsp;i++)&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Thread&amp;nbsp;thread&amp;nbsp;=&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;Thread(()&amp;nbsp;=&amp;gt;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;while&lt;/span&gt;&amp;nbsp;(&lt;span class="Keyword"&gt;true&lt;/span&gt;)&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;nbsp;row;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;lock&lt;/span&gt;&amp;nbsp;(rowsLeft)&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;if&lt;/span&gt;&amp;nbsp;(rowsLeft.Count&amp;nbsp;==&amp;nbsp;0)&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;break&lt;/span&gt;;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;row&amp;nbsp;=&amp;nbsp;rowsLeft.Dequeue();&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;nbsp;index&amp;nbsp;=&amp;nbsp;row&amp;nbsp;*&amp;nbsp;Width;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Statement"&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;nbsp;col&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;col&amp;nbsp;&amp;lt;&amp;nbsp;Width;&amp;nbsp;col++)&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Data[index++]&amp;nbsp;=&amp;nbsp;ComputeMandelbrotIndex(row,&amp;nbsp;col);&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;thread.Start();&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threads.Add(thread);&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threads.ForEach(thread&amp;nbsp;=&amp;gt;&amp;nbsp;thread.Join());&lt;br /&gt;
}
&lt;/div&gt;

&lt;h4&gt;SingleThreadLinqSimple&lt;/h4&gt; &lt;p&gt;This is the &lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx"&gt;first version&lt;/a&gt; of Mandelbrot generation I wrote since thinking of using LINQ, tweaked a litte to dump the output into an existing array instead of creating a new one. It&amp;#39;s essentially the same as SingleThreadImperativeSimple but with the &lt;code&gt;for&lt;/code&gt; loops replaced with a query expression. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index=0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt; b &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = b;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;ParallelLinqRowByRowWithCopy&lt;/h4&gt; &lt;p&gt;My first attempt using Parallel LINQ was &lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2007/12/04/a-cautionary-parallel-tale-ordering-isn-t-simple.aspx"&gt;somewhat disastrous&lt;/a&gt; due to the nature of PLINQ&amp;#39;s ordering. To combat this effect, I initially computed a row at a time, then copying each row into place afterwards: &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] ComputeMandelbrotRow(&lt;span class="ValueType"&gt;int&lt;/span&gt; row)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;byte&lt;/span&gt;[] ret = &lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[Width];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ret[col] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; ret;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel(ParallelQueryOptions.PreserveOrdering)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotRow(row);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; rowStart = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] row &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Array.Copy(row, 0, Data, rowStart, Width);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; rowStart += Width;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;ParallelLinqRowByRowInPlace&lt;/h4&gt; &lt;p&gt;An obvious potential improvement is to write the data to the eventual target array as we go, to avoid creating the extra array creation and copying. At this point we have less of a purely functional solution, but it&amp;#39;s interesting anyway. Note that to use this idea in a query expression we have to return &lt;i&gt;something&lt;/i&gt; even though it&amp;#39;s not useful. Likewise we have to make the query execute fully - so I use &lt;code&gt;Max()&lt;/code&gt; to ensure that all the results will be computed. (I originally tried &lt;code&gt;Count()&lt;/code&gt; but that didn&amp;#39;t work - presumably because the count of the results is known before the actual values are.) As we&amp;#39;re writing the data to the right place on each iteration, we no longer need to preserve ordering. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt; ComputeMandelbrotRow(&lt;span class="ValueType"&gt;int&lt;/span&gt; row)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = row * Width;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; 0;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotRow(row);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; query.Max();&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;ParallelLinqWithSequenceOfPoints&lt;/h4&gt; &lt;p&gt;After this initial foray into Parallel LINQ, Nicholas Palladinos suggested that I could adapt the original idea in a more elegant manner by treating the sequence of points as a single input sequence, and asking Parallel LINQ to preserve the order of that whole sequence. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; points = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; { row, col };&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; point &lt;span class="Statement"&gt;in&lt;/span&gt; points.AsParallel(ParallelQueryOptions.PreserveOrdering)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(point.row, point.col);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index=0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt; b &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = b;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;SingleThreadLinqWithGenerator&lt;/h4&gt; &lt;p&gt;My next step was to try to put the &lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx"&gt;whole guts of the algorithm into the query expression&lt;/a&gt;, using a &lt;code&gt;Complex&lt;/code&gt; value type and a generator to create a sequence of complex numbers generated from the starting point. This is quite a natural step, as the value is computed by just considering this infinite sequence and seeing how quickly it escapes from the circle of radius 2 on the complex plane. Aside from anything else, I think it&amp;#39;s a nice example of deferred execution and streaming - the sequence really would go on forever if we didn&amp;#39;t limit it with the &lt;code&gt;Take&lt;/code&gt; and &lt;code&gt;TakeWhile&lt;/code&gt; calls, but the generator itself doesn&amp;#39;t know it&amp;#39;s being limited. This version uses a sequence of points as per ParallelLinqWithSequenceOfPoints to make it easier to parallelise later. &lt;/p&gt; &lt;p&gt;It&amp;#39;s not exactly an efficient way of doing things though - there&amp;#39;s a huge amount of calling going on from one iterator to another. I&amp;#39;m actually quite surprised that the final results aren&amp;#39;t worse than they are. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; points = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; { row, col };&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; point &lt;span class="Statement"&gt;in&lt;/span&gt; points&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the initial complex value from the row and column&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;let&lt;/span&gt; c = &lt;span class="Keyword"&gt;new&lt;/span&gt; Complex((point.col * SampleWidth) / Width + OffsetX,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (point.row * SampleHeight) / Height + OffsetY)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the number of iterations&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; GenerateSequence(c, x =&amp;gt; x * x + c).TakeWhile(x =&amp;gt; x.SquareLength &amp;lt; 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Take(MaxIterations)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Count() &lt;span class="Linq"&gt;into&lt;/span&gt; count&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Map that to an appropriate byte value&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)(count == MaxIterations ? 0 : (count % 255) + 1);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt; b &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = b;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;static&lt;/span&gt; IEnumerable&amp;lt;T&amp;gt; GenerateSequence&amp;lt;T&amp;gt;(T start, Func&amp;lt;T, T&amp;gt; step)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; T value = start;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;while&lt;/span&gt; (&lt;span class="Keyword"&gt;true&lt;/span&gt;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;yield&lt;/span&gt;&amp;nbsp;&lt;span class="Statement"&gt;return&lt;/span&gt; value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; value = step(value);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;ParallelLinqWithGenerator&lt;/h4&gt; &lt;p&gt;From the previous implementation, parallelisation is simple. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; points = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; { row, col };&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; point &lt;span class="Statement"&gt;in&lt;/span&gt; points.AsParallel(ParallelQueryOptions.PreserveOrdering)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the initial complex value from the row and column&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;let&lt;/span&gt; c = &lt;span class="Keyword"&gt;new&lt;/span&gt; Complex((point.col * SampleWidth) / Width + OffsetX,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (point.row * SampleHeight) / Height + OffsetY)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the number of iterations&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; GenerateSequence(c, x =&amp;gt; x * x + c).TakeWhile(x =&amp;gt; x.SquareLength &amp;lt; 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Take(MaxIterations)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Count() &lt;span class="Linq"&gt;into&lt;/span&gt; count&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Map that to an appropriate byte value&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)(count == MaxIterations ? 0 : (count % 255) + 1);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt; b &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = b;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;SingleThreadImperativeWithComplex&lt;/h4&gt; &lt;p&gt;Having already seen how slow the generator version was in the past, I thought it would be worth checking whether or not this was due to the use of the &lt;code&gt;Complex&lt;/code&gt; struct - so this is a version which uses that, but is otherwise just like the very first implementation (SingleThreadImperativeSimple).&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; row = 0; row &amp;lt; Height; row++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = ComputeMandelbrotIndexWithComplex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;byte&lt;/span&gt; ComputeMandelbrotIndexWithComplex(&lt;span class="ValueType"&gt;int&lt;/span&gt; row, &lt;span class="ValueType"&gt;int&lt;/span&gt; col)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; x = (col * SampleWidth) / Width + OffsetX;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;double&lt;/span&gt; y = (row * SampleHeight) / Height + OffsetY;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Complex start = &lt;span class="Keyword"&gt;new&lt;/span&gt; Complex(x, y);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Complex current = start;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; i = 0; i &amp;lt; MaxIterations; i++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;if&lt;/span&gt; (current.SquareLength &amp;gt;= 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)((i % 255) + 1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; current = current * current + start;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; 0;&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;UnorderedParallelLinqSimple&lt;/h4&gt; &lt;p&gt;This is where my colleague, Christian, came in. He suggested that instead of ordering the results, we just return the point as well as its value. We can then populate the array directly, regardless of the order of the results. The first version just uses an anonymous type to represent the combination:&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; { row, col, value = ComputeMandelbrotIndex(row, col) };&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="Linq"&gt;var&lt;/span&gt; x &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[x.row * Width + x.col] = x.value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;UnorderedParallelLinqWithStruct&lt;/h4&gt; &lt;p&gt;Creating a new garbage-collected object on the heap for each point isn&amp;#39;t the world&amp;#39;s greatest idea. A very simple transformation is to use a struct instead. Without knowing the PLINQ implementation, it seems likely that the values will still end up on the heap somehow (how else could they be communicated between threads?) but I&amp;#39;d still expect a certain saving from this simple step. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; Result (row, col, ComputeMandelbrotIndex(row, col));&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="Linq"&gt;var&lt;/span&gt; x &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[x.Row * Width + x.Column] = x.Value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;struct&lt;/span&gt; Result&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt; row, col;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt; value;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt; Row { get { &lt;span class="Statement"&gt;return&lt;/span&gt; row; } }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;int&lt;/span&gt; Column { get { &lt;span class="Statement"&gt;return&lt;/span&gt; col; } }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt; Value { get { &lt;span class="Statement"&gt;return&lt;/span&gt; value; } }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;internal&lt;/span&gt; Result(&lt;span class="ValueType"&gt;int&lt;/span&gt; row, &lt;span class="ValueType"&gt;int&lt;/span&gt; col, &lt;span class="ValueType"&gt;byte&lt;/span&gt; value)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.row = row;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.col = col;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.value = value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;UnorderedParallelLinqInPlace&lt;/h4&gt; &lt;p&gt;Why bother returning the data at all? We can just write the data in place, like we did earlier with ParallelLinqRowByRowInPlace but in a more aggressive fashion (and the disadvantage of computing the index for each point). Again, we have to return a dummy value and iterate through those dummy results to force all the computations to go through. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Note side-effect!&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; Data[row*Width + col] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Force iteration through all results&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; query.Max();&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;UnorderedParallelLinqInPlaceWithDelegate&lt;/h4&gt; &lt;p&gt;UnorderedParallelLinqInPlace feels slightly nasty - we&amp;#39;ve clearly got a side-effect within the loop, it&amp;#39;s just that we know the side-effects are all orthogonal. We can make ourselves feel slightly cleaner by separating the side-effect from the main algorithm, by way of a delegate. The loop can hold its hands up and say, &amp;quot;I&amp;#39;m side-effect free if you are.&amp;quot; It&amp;#39;s not hugely satisfactory, but it&amp;#39;ll be interesting to see the penalty we pay for this attempt to be purer. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;IEnumerable&amp;lt;T&amp;gt; Generate&amp;lt;T&amp;gt;(Func&amp;lt;&lt;span class="ValueType"&gt;int&lt;/span&gt;, &lt;span class="ValueType"&gt;int&lt;/span&gt;, T&amp;gt; transformation)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Side-effect only if transformation contains one...&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; transformation(row, col);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; query;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Transformation with side-effect&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Func&amp;lt;&lt;span class="ValueType"&gt;int&lt;/span&gt;,&lt;span class="ValueType"&gt;int&lt;/span&gt;,&lt;span class="ValueType"&gt;byte&lt;/span&gt;&amp;gt; transformation = (row, col) =&amp;gt; Data[row * Width + col] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Generate(transformation).Max();&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;UnorderedParallelLinqInPlaceWithGenerator&lt;/h4&gt; &lt;p&gt;We can easily combine the earlier generator code with any of these new ways of processing the results. Here we use our &amp;quot;algorithm in the query&amp;quot; approach but process the results as we go to avoid ordering issues. &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Height).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, Width)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the initial complex value from the row and column&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;let&lt;/span&gt; c = &lt;span class="Keyword"&gt;new&lt;/span&gt; Complex((col * SampleWidth) / Width + OffsetX,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (row * SampleHeight) / Height + OffsetY)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the number of iterations&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;let&lt;/span&gt; count = GenerateSequence(c, x =&amp;gt; x * x + c).TakeWhile(x =&amp;gt; x.SquareLength &amp;lt; 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Take(MaxIterations)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Count()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Map that to an appropriate byte value - and write it in place&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; Data[row * Width + col]&amp;nbsp; = (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)(count == MaxIterations ? 0 : (count % 255) + 1);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Force execution&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; query.Max();&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h4&gt;ParallelForLoop&lt;/h4&gt; &lt;p&gt;The Parallel Extensions library doesn&amp;#39;t just contain Parallel LINQ. It also has various other building blocks for parallelism, including a parallel for loop. This allows very simple parallelism of our very first generator - we just turn the outside loop into a parallel for loop, turning the previous inner loop into a delegate. We need to move the index variable into the outer loop so there&amp;#39;ll be one per row (otherwise they&amp;#39;d trample on each other) but that&amp;#39;s about it: &lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;void&lt;/span&gt; Generate()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Parallel.For(0, Height, row =&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="ValueType"&gt;int&lt;/span&gt; index = row * Width;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;for&lt;/span&gt; (&lt;span class="ValueType"&gt;int&lt;/span&gt; col = 0; col &amp;lt; Width; col++)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Data[index++] = ComputeMandelbrotIndex(row, col);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; });&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;h3&gt;Results&lt;/h3&gt; &lt;p&gt;A benchmark is nothing without results, so here they are on my dual core laptop, from three test runs. The first is the &amp;quot;default&amp;quot; settings I used to develop the benchmark - nothing hugely strenuous, but enough to see differences. I then tried a larger image with the same maximum number of iterations, then the original size with a larger number of iterations. The results are in alphabetical order because that&amp;#39;s how the test prints them :) Times are in milliseconds.&lt;/p&gt; &lt;table cellpadding="2" cellspacing="0"&gt;  &lt;tr&gt; &lt;th&gt;Implementation&lt;/th&gt; &lt;th&gt;Width=1200; MaxIterations=200&lt;/th&gt; &lt;th&gt;Width=3000; MaxIterations=200&lt;/th&gt; &lt;th&gt;Width=1200; MaxIterations=800&lt;/th&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;MultiThreadRowFetching&lt;/td&gt; &lt;td&gt;380&lt;/td&gt; &lt;td&gt;2479&lt;/td&gt; &lt;td&gt;1311&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;MultiThreadUpFrontSplitImperative&lt;/td&gt; &lt;td&gt;384&lt;/td&gt; &lt;td&gt;2545&lt;/td&gt; &lt;td&gt;2088&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelForLoop&lt;/td&gt; &lt;td&gt;376&lt;/td&gt; &lt;td&gt;2361&lt;/td&gt; &lt;td&gt;1292&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowInPlace&lt;/td&gt; &lt;td&gt;378&lt;/td&gt; &lt;td&gt;2347&lt;/td&gt; &lt;td&gt;1295&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqRowByRowWithCopy&lt;/td&gt; &lt;td&gt;382&lt;/td&gt; &lt;td&gt;2376&lt;/td&gt; &lt;td&gt;1288&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithGenerator&lt;/td&gt; &lt;td&gt;4782&lt;/td&gt; &lt;td&gt;29752&lt;/td&gt; &lt;td&gt;16626&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;ParallelLinqWithSequenceOfPoints&lt;/td&gt; &lt;td&gt;549&lt;/td&gt; &lt;td&gt;3413&lt;/td&gt; &lt;td&gt;1462&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SingleThreadImperativeInline&lt;/td&gt; &lt;td&gt;684&lt;/td&gt; &lt;td&gt;4352&lt;/td&gt; &lt;td&gt;2455&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SingleThreadImperativeSimple&lt;/td&gt; &lt;td&gt;704&lt;/td&gt; &lt;td&gt;4353&lt;/td&gt; &lt;td&gt;2372&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SingleThreadImperativeWithComplex&lt;/td&gt; &lt;td&gt;2795&lt;/td&gt; &lt;td&gt;16720&lt;/td&gt; &lt;td&gt;9800&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SingleThreadLinqSimple&lt;/td&gt; &lt;td&gt;726&lt;/td&gt; &lt;td&gt;4522&lt;/td&gt; &lt;td&gt;2438&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SingleThreadLinqWithGenerator&lt;/td&gt; &lt;td&gt;8902&lt;/td&gt; &lt;td&gt;52586&lt;/td&gt; &lt;td&gt;30075&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalleLinqInPlace&lt;/td&gt; &lt;td&gt;422&lt;/td&gt; &lt;td&gt;2586&lt;/td&gt; &lt;td&gt;1317&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithDelegate&lt;/td&gt; &lt;td&gt;509&lt;/td&gt; &lt;td&gt;3093&lt;/td&gt; &lt;td&gt;1392&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqInPlaceWithGenerator&lt;/td&gt; &lt;td&gt;5046&lt;/td&gt; &lt;td&gt;31657&lt;/td&gt; &lt;td&gt;17026&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParallelLinqSimple&lt;/td&gt; &lt;td&gt;556&lt;/td&gt; &lt;td&gt;3449&lt;/td&gt; &lt;td&gt;1448&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td&gt;UnorderedParalelLinqWithStruct&lt;/td&gt; &lt;td&gt;511&lt;/td&gt; &lt;td&gt;3227&lt;/td&gt; &lt;td&gt;1427&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;Conclusions&lt;/h3&gt; &lt;p&gt;So, what have we learned? Well... bearing in mind that benchmarks like this are often misleading compared with real applications, etc it&amp;#39;s still interesting that:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Parallel Extensions rocks. If I were trying to include a Mandelbrot generation implementation in a production setting, I&amp;#39;d definitely go for the parallel for loop - it&amp;#39;s simple, but works just as well as anything else.&lt;/li&gt; &lt;li&gt;The micro-optimisation of SingleThreadImperativeInline really doesn&amp;#39;t help much, but makes the code harder to understand - just like so many micro-optimisations.&lt;/li&gt; &lt;li&gt;The &amp;quot;generator&amp;quot; form of LINQ really doesn&amp;#39;t perform well at all. It does parallelise pretty well, as you&amp;#39;d expect, but it&amp;#39;s just plain slow.&lt;/li&gt; &lt;li&gt;Part of the slowness is almost certainly due to the use of the &lt;code&gt;Complex&lt;/code&gt; struct, given the results of SingleThreadImperativeWithComplex. Not sure what&amp;#39;s going on there.&lt;/li&gt; &lt;li&gt;The extra abstraction step from UnorderedParallelLinqInPlace to UnorderedParallelLinqInPlaceWithDelegate does have a significant impact, at least when the maximum number of iterations per point isn&amp;#39;t the dominant force. That doesn&amp;#39;t mean it would be a bad idea in production, of course - just somewhere to consider when running against performance issues.&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;I suspect I&amp;#39;ve missed some notable points though - comments?&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1624136" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Wacky+Ideas/default.aspx">Wacky Ideas</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>Visualising the Mandelbrot set with LINQ - yet again</title><link>http://msmvps.com/blogs/jon_skeet/archive/2008/02/26/visualising-the-mandelbrot-set-with-linq-yet-again.aspx</link><pubDate>Tue, 26 Feb 2008 20:16:28 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1525235</guid><dc:creator>skeet</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1525235</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1525235</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2008/02/26/visualising-the-mandelbrot-set-with-linq-yet-again.aspx#comments</comments><description>&lt;p&gt;I&amp;#39;ve been thinking about ranges again, particularly after &lt;a href="http://csharpindepth.com/ViewNote.aspx?NoteID=48"&gt;catching a book error&lt;/a&gt; just in time, and looking at Marc&amp;#39;s &lt;a href="http://csharpindepth.com/ViewNote.aspx?NoteID=50"&gt;generic complex type&lt;/a&gt;. It struck me that my &lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx"&gt;previous&lt;/a&gt; &lt;a href="http://msmvps.com/blogs/jon.skeet/archive/2007/12/04/a-cautionary-parallel-tale-ordering-isn-t-simple.aspx"&gt;attempts&lt;/a&gt; were all very well, and demonstrated parallelisation quite neatly, but weren&amp;#39;t very LINQy. In particular, they certainly didn&amp;#39;t use LINQ for the tricky part in the same way that &lt;a href="http://blogs.msdn.com/lukeh/archive/2007/04/03/a-ray-tracer-in-c-3-0.aspx"&gt;Luke Hoban&amp;#39;s ray tracer&lt;/a&gt; does.&lt;/p&gt; &lt;p&gt;The thing is, working out the &amp;quot;value&amp;quot; of a particular point in the Mandelbrot set visualisation is actually quite well suited to LINQ:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Start with a complex value  &lt;li&gt;Apply a transformation to it (square the current value, add the original value from step 1).  &lt;li&gt;Does the result have a modulus &amp;gt; 2? If so, stop.  &lt;li&gt;Have we performed as many iterations as we&amp;#39;re willing to? If so, stop.  &lt;li&gt;Take our new value, and go back to 2.&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;We only need two &amp;quot;extra&amp;quot; bits of code to implement this: a &lt;code&gt;Complex&lt;/code&gt; type, and a way of applying the same transformation repeatedly.&lt;/p&gt; &lt;p&gt;Well, here&amp;#39;s the &lt;code&gt;Complex&lt;/code&gt; type - it contains nothing beyond what we need for this demo, but it&amp;#39;ll do. Obviously Marc&amp;#39;s generic implementation is rather more complete.&lt;/p&gt; &lt;p&gt; &lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;struct&lt;/span&gt; Complex&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;double&lt;/span&gt; real;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;readonly&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;double&lt;/span&gt; imaginary;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt; Complex(&lt;span class="ValueType"&gt;double&lt;/span&gt; real, &lt;span class="ValueType"&gt;double&lt;/span&gt; imaginary)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.real = real;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Keyword"&gt;this&lt;/span&gt;.imaginary = imaginary;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;static&lt;/span&gt; Complex &lt;span class="Keyword"&gt;operator&lt;/span&gt; +(Complex c1, Complex c2)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;static&lt;/span&gt; Complex &lt;span class="Keyword"&gt;operator&lt;/span&gt; *(Complex c1, Complex c2)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt; Complex(c1.real*c2.real - c1.imaginary*c2.imaginary, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; c1.real*c2.imaginary + c2.real*c1.imaginary);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;double&lt;/span&gt; SquareLength&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get { &lt;span class="Statement"&gt;return&lt;/span&gt; real * real + imaginary * imaginary; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;Simple stuff, assuming you know anything about complex numbers.&lt;/p&gt; &lt;p&gt;The other new piece of code is even simpler. It&amp;#39;s just a &lt;em&gt;generator&lt;/em&gt;. It&amp;#39;s a static method which takes an initial value, and a delegate to apply to one value to get the next. It then lazily returns the generated sequece - forever.&lt;/p&gt; &lt;p&gt; &lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;static&lt;/span&gt; IEnumerable&amp;lt;T&amp;gt; Generate&amp;lt;T&amp;gt;(T start, Func&amp;lt;T, T&amp;gt; step)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; T value = start;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;while&lt;/span&gt; (&lt;span class="Keyword"&gt;true&lt;/span&gt;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; yield &lt;span class="Statement"&gt;return&lt;/span&gt; value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; value = step(value);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;Just as an example of use, remember &lt;code&gt;Enumerable.Range&lt;/code&gt; which starts at a particular integer, then adds one repeatedly until it&amp;#39;s yielded as many results as you&amp;#39;ve asked for? Well, here&amp;#39;s a possible implementation, given the &lt;code&gt;Generate&lt;/code&gt; method:&lt;/p&gt; &lt;p&gt; &lt;div class="code"&gt;&lt;span class="Modifier"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span class="Modifier"&gt;static&lt;/span&gt; IEnumerable&amp;lt;&lt;span class="ValueType"&gt;int&lt;/span&gt;&amp;gt; Range(&lt;span class="ValueType"&gt;int&lt;/span&gt; start, &lt;span class="ValueType"&gt;int&lt;/span&gt; count)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Statement"&gt;return&lt;/span&gt; Generate(start, x =&amp;gt; x + 1).Take(count);&lt;br /&gt;} &lt;/div&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;These are all the building blocks we require to build our Mandelbrot visualisation. We want a query which will return a sequence of bytes, one per pixel, where each byte represents the colour to use. Anything which goes beyond our maximum number of iterations ends up black (colour 0) and other values will cycle round the colours. I won&amp;#39;t show the drawing code, but the query is now more self-contained:&lt;/p&gt; &lt;p&gt; &lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the initial complex value from the row and column&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;let&lt;/span&gt; c = &lt;span class="Keyword"&gt;new&lt;/span&gt; Complex((col * SampleWidth) / ImageWidth + OffsetX,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (row * SampleHeight) / ImageHeight + OffsetY)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Work out the number of iterations&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; Generate(c, x =&amp;gt; x * x + c).TakeWhile(x =&amp;gt; x.SquareLength &amp;lt; 4)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Take(MaxIterations)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Count() &lt;span class="Linq"&gt;into&lt;/span&gt; count&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="InlineComment"&gt;// Map that to an appropriate byte value&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;)(count == MaxIterations ? 0 : (count % 255) + 1); &lt;/div&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;(The various constants used in the expression are defined elsewhere.) This works, and puts the Mandelbrot logic directly into the query. However, I have to admit that it&amp;#39;s &lt;i&gt;much&lt;/i&gt; slower than my earlier versions. Heck, I&amp;#39;m still proud of it though.&lt;/p&gt; &lt;p&gt;As ever, &lt;a href="http://pobox.com/~skeet/csharp/blogfiles/LinqMandelbrot.zip"&gt;full source code is available for download&lt;/a&gt;, should you so wish. &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1525235" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Wacky+Ideas/default.aspx">Wacky Ideas</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>A cautionary parallel tale: ordering isn't simple</title><link>http://msmvps.com/blogs/jon_skeet/archive/2007/12/04/a-cautionary-parallel-tale-ordering-isn-t-simple.aspx</link><pubDate>Tue, 04 Dec 2007 17:08:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1379037</guid><dc:creator>skeet</dc:creator><slash:comments>4</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1379037</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1379037</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2007/12/04/a-cautionary-parallel-tale-ordering-isn-t-simple.aspx#comments</comments><description>&lt;p&gt;A little while ago, I wrote about my silly project to test Parallel LINQ - a &lt;a href="http://msmvps.com/blogs/jon_skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx"&gt;Mandelbrot generator&lt;/a&gt;. In the last week, two things have happened to make this more of a reality. Firstly, the &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=e848dc1d-5be3-4941-8705-024bc7f180ba&amp;amp;displaylang=en"&gt;December CTP of Parallel FX&lt;/a&gt; has been released. Secondly, my old laptop died, &amp;quot;forcing&amp;quot; me to get a new laptop, which just happens to have a dual core processor.&lt;/p&gt;
&lt;p&gt;So, it should just be a case of running it, right? Well, not quite. First let&amp;#39;s have a look at the query expression again, in its serial form:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col); &lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;And here&amp;#39;s what &lt;em&gt;should&lt;/em&gt; be generated. &lt;/p&gt;
&lt;p&gt;&lt;img src="http://pobox.com/~skeet/csharp/blogfiles/Mandelbrot.png" alt="" /&gt; &lt;/p&gt;
&lt;p&gt;That&amp;#39;s the result of running without any parallelism, in other words. Now, I realised from the start that we would need ordering, but my first experiment was just to call AsParallel() to see what would happen:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight).AsParallel()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col); &lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;As expected, that didn&amp;#39;t produce quite the result we wanted:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://pobox.com/~skeet/csharp/blogfiles/UnorderedParallelRow.png" alt="" /&gt; &lt;/p&gt;
&lt;p&gt;Well, that&amp;#39;s okay. I wanted to prove that ordering was necessary, and indeed that&amp;#39;s fairly obvious from the result. There are horizontal blocks, returned out of order. Easily fixable, right? After all, I posted what I thought would be the solution with the original post. We just need to give the appropriate option as a method parameter:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight).AsParallel(ParallelQueryOptions.PreserveOrdering)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col); &lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Not so fast. It certainly changed things, but not quite as hoped:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://pobox.com/~skeet/csharp/blogfiles/OrderedParallelRow.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;I haven&amp;#39;t yet analysed quite why we now have the rows in order but the &lt;i&gt;columns&lt;/i&gt; out of order. However, I haven&amp;#39;t quite managed to fix it with the code in its original form. I &lt;i&gt;have&lt;/i&gt; managed to fix it by reducing it from two (implicit) loops to one:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight).AsParallel(ParallelQueryOptions.PreserveOrdering)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotRow(row);&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] data = &lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[ImageHeight * ImageWidth];&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;int&lt;/span&gt; rowStart = 0;&lt;br /&gt;&lt;span class="Statement"&gt;foreach&lt;/span&gt; (&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] row &lt;span class="Statement"&gt;in&lt;/span&gt; query)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Array.Copy(row, 0, data, rowStart, ImageWidth);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; rowStart += ImageWidth;&lt;br /&gt;} &lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Instead of getting all the results in one go (with a call to ToArray()) we now have to reassemble all the data into a block. Still, it achieves the desired result. I should point out that PFX has better ways of doing this, Parallel.For being an obvious starting point from the little I know of it. At some point I&amp;#39;ll get round to reading about them, but at the moment life&amp;#39;s too short. I should also say that I don&amp;#39;t expect that any of the pictures indicate a bug in Parallel LINQ, merely my understanding of it.&lt;/p&gt;
&lt;h3&gt;Update: Explanation and a workaround&lt;/h3&gt;
&lt;p&gt;Having asked about this on the &lt;a href="http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=1986&amp;amp;SiteID=1"&gt;Connect forum for PFX&lt;/a&gt;, Igor Ostrovsky has explained that this is by design - currently only outer ordering is preserved when requested. The issue is still open, however - it&amp;#39;s possible that it will change before release.&lt;/p&gt;
&lt;p&gt;In the meantime, Nicholas Palladinos has come up with an alternative solution which I rather like. I&amp;#39;ve refactored it a bit, but the basic idea is to turn the two sequences into one before parallelisation:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class="code"&gt; &lt;span class="Linq"&gt;var&lt;/span&gt;&amp;nbsp;points&amp;nbsp;=&amp;nbsp;&lt;span class="Linq"&gt;from&lt;/span&gt;&amp;nbsp;row&amp;nbsp;&lt;span class="Statement"&gt;in&lt;/span&gt;&amp;nbsp;Enumerable.Range(0,&amp;nbsp;ImageHeight)&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Linq"&gt;from&lt;/span&gt;&amp;nbsp;col&amp;nbsp;&lt;span class="Statement"&gt;in&lt;/span&gt;&amp;nbsp;Enumerable.Range(0,&amp;nbsp;ImageWidth)&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;&lt;span class="Keyword"&gt;new&lt;/span&gt;&amp;nbsp;{&amp;nbsp;row,&amp;nbsp;col&amp;nbsp;};&lt;br /&gt; &lt;br /&gt; &lt;span class="Linq"&gt;var&lt;/span&gt;&amp;nbsp;query&amp;nbsp;=&amp;nbsp;&lt;span class="Linq"&gt;from&lt;/span&gt;&amp;nbsp;point&amp;nbsp;&lt;span class="Statement"&gt;in&lt;/span&gt;&amp;nbsp;points.AsParallel(ParallelQueryOptions.PreserveOrdering)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="Linq"&gt;select&lt;/span&gt;&amp;nbsp;ComputeMandelbrotIndex(point.row,&amp;nbsp;point.col); &lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;That works really well - in fact, more than twice as fast as the serial version, on my 2-core box!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1379037" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item><item><title>LINQ to Silliness: Generating a Mandelbrot with parallel potential</title><link>http://msmvps.com/blogs/jon_skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx</link><pubDate>Wed, 03 Oct 2007 20:12:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1229473</guid><dc:creator>skeet</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1229473</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1229473</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2007/10/03/linq-to-silliness-generating-a-mandelbrot-with-parallel-potential.aspx#comments</comments><description>
&lt;p&gt;I&amp;#39;ve been writing about LINQ recently, and in particular I&amp;#39;ve written a small amount about &lt;a href="http://msdn.microsoft.com/msdnmag/issues/07/10/PLINQ/default.aspx"&gt;Parallel LINQ&lt;/a&gt;. (Don&amp;#39;t get excited - it&amp;#39;s only about a page, just to mention it as a sort of &amp;quot;meta-provider&amp;quot; for LINQ.) I was wondering what to use to demonstrate it - what general task can we perform which could take a lot of CPU?&lt;/p&gt;
 
&lt;p&gt;Well, I used to be quite into fractals, and I&amp;#39;ve written Mandelbrot set generators in various languages. I hadn&amp;#39;t done it in C# before now, however. Calculating the colour of each pixel is completely independent of all the other pixels - it&amp;#39;s an &amp;quot;embarrassingly parallelizable&amp;quot; task. So, a great task for PLINQ. Here&amp;#39;s the &amp;quot;normal LINQ&amp;quot; code:&lt;/p&gt;
 
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
 
&lt;table class="code"&gt;  
&lt;tr&gt; 
&lt;td&gt;
&lt;pre&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight)&lt;br /&gt;            &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&lt;br /&gt;            &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col);&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] data = query.ToArray();&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Changing this into a parallel query is really simple - although we do need to preserve the ordering of the results: &lt;/p&gt;

&lt;table class="code"&gt;

&lt;tr&gt;
&lt;td&gt;
&lt;pre&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; query = &lt;span class="Linq"&gt;from&lt;/span&gt; row &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageHeight).AsParallel(QueryOptions.PreserveOrdering)&lt;br /&gt;            &lt;span class="Linq"&gt;from&lt;/span&gt; col &lt;span class="Statement"&gt;in&lt;/span&gt; Enumerable.Range(0, ImageWidth)&lt;br /&gt;            &lt;span class="Linq"&gt;select&lt;/span&gt; ComputeMandelbrotIndex(row, col);&lt;br /&gt;&lt;br /&gt;&lt;span class="ValueType"&gt;byte&lt;/span&gt;[] data = query.ToArray();&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Without being able to actually use PLINQ yet, I can&amp;#39;t tell how awful the order preservation is - Joe warns that it&amp;#39;s costly, but we&amp;#39;ll see. This is on a pretty giant sequence of data, of course... An alternative would be to parallelize a row at a time, but that loses some of the purity of the solution. This is a very, very silly way of parallelizing the task, but it&amp;#39;s got a certain quirky appeal.&lt;/p&gt;

&lt;p&gt;Of course, there&amp;#39;s then the code for &lt;code&gt;ComputeMandelBrotIndex&lt;/code&gt; and displaying a bitmap from it - the &lt;a href="http://pobox.com/%7Eskeet/csharp/blogfiles/Mandelbrot.cs"&gt;full code is available for download&lt;/a&gt; (it&amp;#39;s a single C# file - just compile and run). Enjoy.&lt;/p&gt;
&lt;h4&gt;Update!&lt;/h4&gt;
&lt;p&gt;This blog post has been picked up by Nick Palladinos, who has written &lt;a href="http://www.dotnetzone.gr/cs/blogs/palladin/archive/2007/09/01/parallel-linq.aspx"&gt;his own Parallel LINQ provider&lt;/a&gt; (much kudos for that - unfortunately for me the blog is in Greek, which I don&amp;#39;t understand). Apparently on a dual core processor the parallelised version of the Mandelbrot generator is indeed about twice as fast - it works! Unfortunately I can&amp;#39;t tell as my laptop only has a single core... it&amp;#39;s very exciting though :)
&lt;/p&gt;
&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1229473" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/General/default.aspx">General</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Books/default.aspx">Books</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/LINQ/default.aspx">LINQ</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Parallelisation/default.aspx">Parallelisation</category></item></channel></rss>