4

I'm trying to implement PLINQ example but facing following problem My sequential queries are executed faster than parallel queries.

here is the code example:

        Stopwatch sw = new Stopwatch();

        int[] vals = Enumerable.Range(0, Int16.MaxValue).ToArray();

        sw.Start();
        int[] x1 = vals.Where(x => x % 2 == 0).ToArray();
        sw.Stop();
        Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);


        sw.Restart();
        int[] x2 = vals.AsParallel().Where(x => x % 2 == 0).ToArray();
        sw.Stop();
        Console.WriteLine("Parallel Execution {0} milliseconds", sw.ElapsedMilliseconds);

My machine is Pentium(R) Dual - Core I've also tried on Quad - Core AMD Opteron(tm).

The same result Parallel queries run slower than sequential. Can you please tell me what is my problem?

Thanks.

Michael Samteladze
  • 1,310
  • 15
  • 38
  • 1
    http://stackoverflow.com/questions/7582591/how-to-plinq-an-existing-linq-query-with-joins (read my recommended readings). In short: your modulo operation is too trivial. You need more complex operations. – Tim Schmelter Jun 06 '12 at 14:39

3 Answers3

5

I guess this has to do with some overhead. The collection you iterate is quite small (32k shorts) and the operation performed on those items is trivial.

In such a scenario the partitioning of a collection, filtering and remerging may be much more expensive than performing the same within a single iteration.

If your comparison is more expensive (e.g. searching strings) and your collection grows, you´ll see the results changing.

Joachim Kerschbaumer
  • 9,695
  • 7
  • 49
  • 84
  • Yes, PLINQ is not a silver bullet that will speed up every query. It shines when there is a delay when processing each item, like loading it from DB. Since there are no delays in your queries and you max out CPU anyway the overhead of PLINQ will slow you down. – Jakub Konecki Jun 06 '12 at 14:39
4

Your 'Problem' is using PLINQ when it doesn't make sense

PLINQ won't always be faster. PLINQ will ALWAYS add overhead.

In terms of CPU instructions; whatever amount of work you need to do (call it X) you will end up executing more than X instructions. PLINQ is going to do a lot of extra work kicking off threads, delegating work to them and getting the results back into a form you can work with.

The advantage to doing this is you can have more than one CPU/Core doing work. Sometimes it is faster. When the amount of CPU work you are doing is small relative to the overhead, it will be slower.

When I run your code I get the following output:

Sequential Execution 2 milliseconds

Parallel Execution 40 milliseconds

I can also see eight worker threads being created by the PLINQ code. Those eight threads represent a lot of overhead for 2 milliseconds worth of computation. You can get a feel for how much overhead it is by running your Parallel Execution benchmark twice. The worker threads will hang around. Here's my output with running it a second time:

Sequential Execution 2 milliseconds

Parallel #1 Execution 40 milliseconds

Parallel #2 Execution 3 milliseconds

The second time is much faster; but still slower than not doing anything. Because, even with the worker threads already created - PLINQ still has to do work to divide up the operations between the threads and get the results back in a format you can access.

The more work you have to do, the less impact the overhead will be. In this example, I've replaced your Where lambda with a static function called IsValid and I compute the %2 500 times instead of only once.

static bool IsValid(int input)
{
    int result=0;

    for(int i =0;i<500;i++)            
        result = input%2;

    return result == 0;
}

Now - my execution times are:

Sequential Execution 36 milliseconds

Parallel #1 Execution 47 milliseconds

Parallel #2 Execution 9 milliseconds

You can see that PLINQ is still slower on the first execution - but significantly faster on the second. If you up CPU work by increasing the loop from 500 to 5000 (on my machine) PLINQ wins, hands down.

TL;DR - You are doing right; you just aren't doing enough work to make PLINQ the faster choice.

Here's the entire source code for what I've done:

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();

    int[] vals = Enumerable.Range(0, Int16.MaxValue).ToArray();

    sw.Start();
    int[] x1 = vals.Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);

    sw.Restart();
    int[] x2 = vals.AsParallel().Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Parallel #1 Execution {0} milliseconds", sw.ElapsedMilliseconds);

    sw.Restart();
    int[] x3 = vals.AsParallel().Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Parallel #2 Execution {0} milliseconds", sw.ElapsedMilliseconds);

    Console.Read();
}

static bool IsValid(int input)
{
    int result=0;

    for(int i =0;i<5000;i++)            
        result = input%2;

    return result == 0;
}
Community
  • 1
  • 1
Rob P.
  • 14,921
  • 14
  • 73
  • 109
  • Are you sure the second parallel query executes faster because the threads already started? If you try to exec the same sequential query twice it is faster too, I just think it is the query cached – MaRuf Jun 19 '12 at 07:59
2

this one seems to work better:

        Stopwatch sw = new Stopwatch();

        int[] vals = Enumerable.Range(0, 10000000).ToArray();

        sw.Start();
        var x1 = vals.Where(x => x % 2 == 0).ToList();
        sw.Stop();
        Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);


        sw.Restart();
        var x2 = vals.Where(x => x % 2 == 0).AsParallel().ToList();
        sw.Stop();
        Console.WriteLine("Parallel Execution {0} milliseconds", sw.ElapsedMilliseconds);

do not start another thread for 200 values. it takes more to start/wake up the other threads than to finish the entire loop on a single thread. + more threads mean thread syncronization mechanism.

LE: Ok, I tried for Int16.MaxValue and it works better there. Not I realized that the maxvalue is around 30k, so the comment may not apply to your case. probably the issue is that AsParralel was missplaced.

Andrei Neagu
  • 898
  • 8
  • 25
  • I just tried some examples from books, I knew that breaking collection to threads with small amount of data would be more time consuming operation for parallel execution, but I didn't realize that 32K count of values is not enough. – Michael Samteladze Jun 06 '12 at 15:14