How can we be sure that the number of iterations of commands in "Parallel.For" matches the number of iterations that were set for it?

Reza Jaferi 331 Reputation points
2022-12-14T03:49:58.633+00:00

Hi guys,
In my article, I want to compare the speed of sequential processing to parallel processing in various conditions, so in one example, I want to convert the following sequential command to parallel, but after converting it to parallel, it seems that the loop is not repeated for the specified number of times. It may be necessary to use Concurrent Collection or a lock statement, but this usually reduces the speed of parallel processing compared to sequential processing.
According to what was said, is there a solution or approach to converting the following code so that the performance does not drop and the number of times the loop is executed is complete so that it can be displayed?

        static long Counter = 0;  
        public static long Calculate()  
        {  
            int Total = 0;  
            for (int i = 0; i < 30; i++)  
            {  
                Total = 20 / 4;  
                Total = 20 * 4;  
                Total = 20 ^ 4;  
                Total = 20 - 4;  
                Total = 20 % 4;  
            }  
            Counter += 30;  
            return Counter;  
        }  

Sequential:

        static void Main(string[] args)  
        {  
            Stopwatch SW = new Stopwatch();  
            Console.ReadKey();  
            SW.Start();  
            for (int i = 0; i < 50000000; i++)  
            {  
                Calculate();  
                Calculate();  
                Calculate();  
                Calculate();  
                Calculate();  
            }  
            SW.Stop();  
            Console.WriteLine("Total seconds: " + SW.Elapsed.TotalSeconds + ", Counter: " + Counter);  
            Console.ReadKey();  
        }  

Result:
270331-sequential.png
Counter value=50000000*30*5=7,500,000,000

Parallel:

        static void Main(string[] args)  
        {  
            Stopwatch SW = new Stopwatch();  
            Console.ReadKey();  
            SW.Start();  
            Parallel.For(0, 50000000, (int i) =>  
            {  
                Calculate();  
                Calculate();  
                Calculate();  
                Calculate();  
                Calculate();  
            });  
            SW.Stop();  
            Console.WriteLine("Total seconds: " + SW.Elapsed.TotalSeconds + ", Counter: " + Counter);  
            Console.ReadKey();  
        }  

One of the results:
270332-parallel.png
Counter value=50000000*30*5= different values are displayed

I use the following tools:

  • .NET Framework 4.5
  • Console app
  • Windows 7

Thank you for your time.
Best regards

.NET CLI
.NET CLI
A cross-platform toolchain for developing, building, running, and publishing .NET applications.
322 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,234 questions
0 comments No comments
{count} votes

Accepted answer
  1. Bruce (SqlWork.com) 55,601 Reputation points
    2022-12-16T20:04:35.247+00:00

    your calculate routine is too fast. it is quicker than the overhead of managing the parallel processing, so sequential is typically faster. you need to make the calculate take longer. also do not use the debugger, as it will affect the parallel process performance.

    using System.Diagnostics;   
    using System.Threading;   
    using System.Threading.Tasks;   
       
    const int LOOP_COUNT = 10000;   
       
    Console.WriteLine($"Call Calulate {LOOP_COUNT} times" );   
    SequentialCalcute();   
    ParallelCalcute();   
       
    long Calcute()   
    {   
        int Total = 0;   
        for (int i = 0; i < 10000; i++) // add more iteration to make parallel faster than sequential  
        {   
            Total = 20 / 4;   
            Total = 20 * 4;   
            Total = 20 ^ 4;   
            Total = 20 - 4;   
            Total = 20 % 4;   
        }   
        return Total;   
    }   
       
    void SequentialCalcute()   
    {   
        var Counter = 0;   
        Stopwatch SW = new Stopwatch();   
        SW.Start();   
        for (int i = 0; i < LOOP_COUNT; i++)   
        {   
            Calcute();   
            ++Counter;   
        }   
        SW.Stop();   
        Console.WriteLine("Sequential Total seconds: " + SW.Elapsed.TotalSeconds + ", Counter: " + Counter);   
    }   
       
    void ParallelCalcute()   
    {   
        var Counter = 0;   
        Stopwatch SW = new Stopwatch();   
        SW.Start();   
        Parallel.For(0, LOOP_COUNT, i =>   
        {   
            Calcute();   
            Interlocked.Add(ref Counter, 1);   
        });   
        SW.Stop();   
        Console.WriteLine("Parallel Total seconds: " + SW.Elapsed.TotalSeconds + ", Counter: " + Counter);   
    }   
        
    

    Update:

    273449-image.png

    273459-code.txt

    2 people found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. Jiale Xue - MSFT 31,516 Reputation points Microsoft Vendor
    2022-12-14T09:18:55.383+00:00

    Hi @Reza Jaferi ,

    Variables are shared among multiple threads. This means that multiple threads can read and write to this variable concurrently, which may cause race conditions and other unpredictable behavior.

    For example, when multiple threads try to increment a variable at the same time, the resulting value might not be what you expect.

    If you want it to explicitly show the expected number, then you have to use a lock, which of course incurs additional performance overhead.

    Below is the modification using lock:

    static long Counter = 0;  
    private static object _lock = new object();  
      
    public static long Calculate()  
    {  
        int Total = 0;  
        for (int i = 0; i < 30; i++)  
        {  
            Total = 20 / 4;  
            Total = 20 * 4;  
            Total = 20 ^ 4;  
            Total = 20 - 4;  
            Total = 20 % 4;  
        }  
      
        // Use a lock statement   
        lock (_lock)  
        {  
            Counter += 30;  
        }  
      
        return Counter;  
    }  
    

    Update:

    As mentioned, the parallel approach doesn't affect how many times you should run it, but you have to understand when to use parallelism.

    If overlapping of data occurs, you must secure the data with a lock or something similar.

    If your data does not overlap, like the addition of 100 random numbers, you can get the performance improvement you expect by using parallelism.

    Parallelism does not exist in this code to improve what you expect, I hope you could understand.

    This method will run the specified number of times, you don't need to worry about it.

    Best Regards,
    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. PatriceSc 166 Reputation points
    2022-12-21T17:35:59.58+00:00

    Hi,

    For a proof of concept you could perhaps show a real world calculation whose result itself shows it is correct rather than introducing an artificial verification counter that needs to be locked ?

    0 comments No comments