5.3. Parallel Loops

One of the easiest ways to parallelize your application is by using the parallel loop construct. Two types of loops can be run in parallel:

  • Parallel.For()

  • Parallel.ForEach()

Let's take a look at these now.

5.3.1. Parallel.For()

In our example application, we will stick with the stock quote scenario described previously, create a list of stock quotes, and then iterate through them using a Parallel.For() loop construct, passing each quote into a function that will simulate a long-running process.

To see the differences between running code in serial and parallel, we will also perform this task using a standard for loop. We will use a stopwatch instance to measure the time each loop takes to complete. It is worth stressing that you should always measure the performance impact that parallelization can have on your applications.

5.3.1.1. An Unrealistic Example?

Yes. To keep things very simple, we will just call Thread.Sleep() for two seconds and then return a random number to simulate performing a calculation. Most parallelization examples tend to calculate factorials or walk trees of data, but I think this distracts (at least initially) from understanding the basics. If you want to work with a more realistic example, take a look at the examples from the parallel team (see the URL above in the "Performance" section); you will find excellent ray tracing and other math-related examples.

Note that calling the Thread.Sleep() method will involve a context switch (an expensive operation for the CPU), so it might slow the sample application down more than performing work might have.

  1. Create a new console application called Chapter5.HelloParalleland add the following using directives:

    using System.Diagnostics;
    using System.Threading.Tasks;

  2. Amend Program.cs to the following code:

    class Program
    {
        public static List<StockQuote> Stocks = new List<StockQuote>();
    
        static void Main(string[] args)
        {
            double serialSeconds = 0;
            double parallelSeconds = 0;
    
            Stopwatch sw = new Stopwatch();
    
            PopulateStockList();
    
            sw = Stopwatch.StartNew();
            RunInSerial();
            serialSeconds = sw.Elapsed.TotalSeconds;
    
            sw = Stopwatch.StartNew();
            RunInParallel();
            parallelSeconds = sw.Elapsed.TotalSeconds;
    
            Console.WriteLine(
             "Finished serial at {0} and took {1}", DateTime.Now, serialSeconds);
            Console.WriteLine(
             "Finished parallel at {0} and took {1}", DateTime.Now, parallelSeconds);
    
            Console.ReadLine();
    
        }

    private static void PopulateStockList()
        {
            Stocks.Add(new StockQuote { ID = 1, Company = "Microsoft", Price = 5.34m });
            Stocks.Add(new StockQuote { ID = 2, Company = "IBM", Price = 1.9m });
            Stocks.Add(new StockQuote { ID = 3, Company = "Yahoo", Price = 2.34m });
            Stocks.Add(new StockQuote { ID = 4, Company = "Google", Price = 1.54m });
            Stocks.Add(new StockQuote { ID = 5, Company = "Altavista", Price = 4.74m });
            Stocks.Add(new StockQuote { ID = 6, Company = "Ask", Price = 3.21m });
            Stocks.Add(new StockQuote { ID = 7, Company = "Amazon", Price = 20.8m });
            Stocks.Add(new StockQuote { ID = 8, Company = "HSBC", Price = 54.6m });
            Stocks.Add(new StockQuote { ID = 9, Company = "Barclays", Price = 23.2m });
            Stocks.Add(new StockQuote { ID = 10, Company = "Gilette", Price = 1.84m });
        }
    
        private static void RunInSerial()
        {
            for (int i = 0; i < Stocks.Count; i++)
            {
                Console.WriteLine("Serial processing stock: {0}",Stocks[i].Company);
                StockService.CallService(Stocks[i]);
                Console.WriteLine();
            }
        }
    
        private static void RunInParallel()
        {
            Parallel.For(0, Stocks.Count, i =>
            {
                Console.WriteLine("Parallel processing stock: {0}", Stocks[i].Company);
                StockService.CallService(Stocks[i]);
                Console.WriteLine();
            });
        }
    }

  3. Create a new class called StockQuote and add the following code:

    public class StockQuote
    {
        public int ID {get; set;}
        public string Company {get; set;}
        public decimal Price{get; set;}
    }

  4. Create a new class called StockService and enter the following code:

    public class StockService
    {
        public static decimal CallService(StockQuote Quote)
        {
            Console.WriteLine("Executing long task for {0}", Quote.Company);
            var rand = new Random(DateTime.Now.Millisecond);
            System.Threading.Thread.Sleep(1000);
            return Convert.ToDecimal(rand.NextDouble());
        }
    }

Press F5 to run the code. When I run the code on my machine, I receive the output shown in Figure 5-2.

Figure 5.2. Output of a Parallel.For() loop against serial processing

Are the stock quotes processed incrementally or in a random order? You might have noted that your application did not necessarily process the stock quotes in the order in which they were added to the list when run in parallel. This is because work was divided between the cores on your machine, so it's important to remember that work might not (and probably won't) be processed sequentially. We will look at how the work is shared in more detail when we look at the new task functionality.

Try running the code again. Do you get similar results? The quotes might be processed in a slightly different order, and speed increases might vary slightly depending on what other applications are doing on your machine. When measuring performance, be sure to perform a number of tests.

Let's now take a look at the syntax used in the Parallel.For() loop example:

System.Threading.Parallel.For(0, Stocks.Count, i =>
{
...
});

The Parallel.For() method actually has 12 different overloads, but this particular version accepts 3 parameters:

  • 0 is the counter for the start of the loop.

  • Stocks.Count lets the loop know when to stop.

  • i=> is our friendly lambda statement (or inline function) with the variable i representing the current iteration, which allows you to query the list of stocks.

5.3.2. ParallelOptions

Some of the various parallel overloads allow you to specify options such as the number of cores to use when running the loop in parallel by using the ParallelOptions class. The following code limits the number of cores to use for processing to two. You might want to do this to ensure cores are available for other applications.

ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 2 };

Parallel.For(0, 100, options, x=>
{
    //Do something
});

5.3.3. Parallel.ForEach()

Similar to the Parallel.For() loop, the Parallel.ForEach() method allows you to iterate through an object supporting the IEnumerable interface:

Parallel.ForEach(Stocks, stock  =>
{
    StockService.CallService(stock);
});

5.3.4. Warning: Parallelization Can Hurt Performance

Parallelizing code contains overhead and can actually slow down your code, including when there are loops that run a very small amounts of code in each iteration. Please refer to the following articles about why this occurs:

5.3.5. Parallel.Invoke()

The Parallel.Invoke() method can be used to execute code in parallel. It has the following syntax:

Parallel.Invoke(()=>StockService.CallService(Stocks[0]),
    () => StockService.CallService(Stocks[1]),
    () => StockService.CallService(Stocks[2])
);

When you use Parallel.Invoke() or any of the parallel loops, the parallel extensions are behind the scenes using tasks. Let's take a look at tasks now.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.7.212