2

want to: sum x and sum x*x. Where x = line[i]. Because more than one thread wants to read/write to the "sumAll" and "sumAllQ" I need to lock its access. The problem is that the lock kind off serializes things here. I would need to split this operation in #"Environment.ProcessorCount" for loops, each one summing one part of the array, and finally summing theirs results. But how can I make it programmatically?

Sample code:

//line is a float[]
Parallel.For(0, line.Length,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
i =>
{
    x = (double)line[i];
    lock (sumLocker)
    {
        sumAll += x;
        sumAllQ += x * x;
    }
});

EDIT 1: Matthew Watson answer Benchmark results

At home. CPU Core 2 Quad Q9550 @ 2.83 GHz:

Result via Linq:      SumAll=49999950000, SumAllQ=3,33332833333439E+15
Result via loop:      SumAll=49999950000, SumAllQ=3,33332833333439E+15
Result via partition: SumAll=49999950000, SumAllQ=3,333328333335E+15
Via Linq took: 00:00:02.6983044
Via Loop took: 00:00:00.4811901
Via Partition took: 00:00:00.1595113

At work. CPU i7 930 2.8 GHz:

Result via Linq:      SumAll=49999950000, SumAllQ=3,33332833333439E+15
Result via loop:      SumAll=49999950000, SumAllQ=3,33332833333439E+15
Result via partition: SumAll=49999950000, SumAllQ=3,333328333335E+15
Via Linq took: 00:00:01.5728736
Via Loop took: 00:00:00.3436929
Via Partition took: 00:00:00.0934209
9
  • Use one of the overloads that lets you use an accumulator. Commented May 29, 2013 at 18:36
  • line.AsParallel().Aggregate ? Commented May 29, 2013 at 18:36
  • What you want is ParallelEnumerable.Aggregate (msdn.microsoft.com/en-us/library/dd383667.aspx) Commented May 29, 2013 at 18:37
  • In this case you can use ParallelEnumerable.Sum, however. Commented May 29, 2013 at 18:40
  • Not I can't use sum because I need to sum x*x too. Commented May 29, 2013 at 18:43

2 Answers 2

5

vcjones wondered about whether you would really see any speedup. Well the answer is: it probably depends how many cores you have. The PLinq is slower than a plain loop on my home PC (which is quad core).

I've come up with an alternative approach which uses a Partitioner to chop the list of numbers up into several sections so you can add up each one separately. There's also some more information about using a Partitioner here.

Using the Partitioner approach seems a bit faster, at least on my home PC.

Here's my test program. Note that you must run a release build of this outside any debugger to get the right timings.

The important method in this code is ViaPartition():

Result ViaPartition(double[] numbers)
{
    var result = new Result();

    var rangePartitioner = Partitioner.Create(0, numbers.Length);

    Parallel.ForEach(rangePartitioner, (range, loopState) =>
    {
        var subtotal = new Result();

        for (int i = range.Item1; i < range.Item2; i++)
        {
            double n = numbers[i];
            subtotal.SumAll  += n;
            subtotal.SumAllQ += n*n;
        }

        lock (result)
        {
            result.SumAll  += subtotal.SumAll;
            result.SumAllQ += subtotal.SumAllQ;
        }
    });

    return result;
}

My results when I run the full test program (shown below these results) are:

Result via Linq:      SumAll=49999950000, SumAllQ=3.33332833333439E+15
Result via loop:      SumAll=49999950000, SumAllQ=3.33332833333439E+15
Result via partition: SumAll=49999950000, SumAllQ=3.333328333335E+15
Via Linq took: 00:00:01.1994524
Via Loop took: 00:00:00.2357107
Via Partition took: 00:00:00.0756707

(Note the slight differences due to rounding errors.)

It'd be interesting to see the results from other systems.

Here's the full test program:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Demo
{
    public class Result
    {
        public double SumAll;
        public double SumAllQ;

        public override string ToString()
        {
            return string.Format("SumAll={0}, SumAllQ={1}", SumAll, SumAllQ);
        }
    }

    class Program
    {
        void run()
        {
            var numbers = Enumerable.Range(0, 1000000).Select(n => n/10.0).ToArray();

            // Prove that the calculation is correct.
            Console.WriteLine("Result via Linq:      " + ViaLinq(numbers));
            Console.WriteLine("Result via loop:      " + ViaLoop(numbers));
            Console.WriteLine("Result via partition: " + ViaPartition(numbers));

            int count = 100;

            TimeViaLinq(numbers, count);
            TimeViaLoop(numbers, count);
            TimeViaPartition(numbers, count);
        }

        void TimeViaLinq(double[] numbers, int count)
        {
            var sw = Stopwatch.StartNew();

            for (int i = 0; i < count; ++i)
                ViaLinq(numbers);

            Console.WriteLine("Via Linq took: " + sw.Elapsed);
        }

        void TimeViaLoop(double[] numbers, int count)
        {
            var sw = Stopwatch.StartNew();

            for (int i = 0; i < count; ++i)
                ViaLoop(numbers);

            Console.WriteLine("Via Loop took: " + sw.Elapsed);
        }

        void TimeViaPartition(double[] numbers, int count)
        {
            var sw = Stopwatch.StartNew();

            for (int i = 0; i < count; ++i)
                ViaPartition(numbers);

            Console.WriteLine("Via Partition took: " + sw.Elapsed);
        }

        Result ViaLinq(double[] numbers)
        {
            return numbers.AsParallel().Aggregate(new Result(), (input, value) => new Result
            {
                SumAll  = input.SumAll+value,
                SumAllQ = input.SumAllQ+value*value
            });
        }

        Result ViaLoop(double[] numbers)
        {
            var result = new Result();

            for (int i = 0; i < numbers.Length; ++i)
            {
                double n = numbers[i];
                result.SumAll  += n;
                result.SumAllQ += n*n;
            }

            return result;
        }

        Result ViaPartition(double[] numbers)
        {
            var result = new Result();

            var rangePartitioner = Partitioner.Create(0, numbers.Length);

            Parallel.ForEach(rangePartitioner, (range, loopState) =>
            {
                var subtotal = new Result();

                for (int i = range.Item1; i < range.Item2; i++)
                {
                    double n = numbers[i];
                    subtotal.SumAll  += n;
                    subtotal.SumAllQ += n*n;
                }

                lock (result)
                {
                    result.SumAll  += subtotal.SumAll;
                    result.SumAllQ += subtotal.SumAllQ;
                }
            });

            return result;
        }

        static void Main()
        {
            new Program().run();
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

So, AsParallel().Aggregate is slower on this case! Why??
@Pedro77 Because the overhead of creating the additional threads, managing the work between them, synchronizing them when needed, etc. is more expensive them what you gain from being able to execute some of the code in parallel. This is partly due to not having a lot of cores on his machine, probably not having enough work to offset the overhead, and having a task that is inherently less appropriate for parallelization in general (each operation can't be done entirely independently; they need to interact with each other).
@Pedro77 I have updated my answer now. If you really want the maximum speed, I have found a faster way (well, it's faster on my PC).
@Pedro77: Aggregate is slower because of the massive method call overhead. Creating and populating all those new objects doesn't help either.
@Matthew Watson, thank you very much for the detailed benchmark. I'll try it as soon as I can on my home and work PC and than I post the results here. About the overhead, take a look here: stackoverflow.com/questions/473782/inline-functions-in-c :)
3

As suggested in the comments, you can use Aggregate to accomplish this with AsParallel in LINQ. For example:

using System.Linq;

//A class to hold the results.
//This can be improved by making it immutable and using a constructor.
public class Result
{
    public double SumAll { get; set; }
    public double SumAllQ { get; set; }
}

And you can use LINQ like so:

var result = line.AsParallel().Aggregate(new Result(), (input, value) => new Result {SumAll = input.SumAll+value, SumAllQ = input.SumAllQ+value*value});

Or even better:

var pline = line.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount);
var result = new Result { SumAll = pline.Sum(), SumAllQ = pline.Sum(x => x * x) };

AsParallel doesn't give you the ability to directly specify options, but you can use .WithDegreeOfParallelism(), .WithExecutionMode(), or .WithMergeOptions() to give you more control. You may have to use WithDegreeOfParallelism to even get it to run with multiple threads.

5 Comments

As an aside, I'm not sure this kind of operation would really benefit from being executed parallel. Have you done any benchmarking to see if parallel execution increases performance? There is still synchronization going on under the covers (albeit not serialized).
Thanks for the answer, I'll do some benchmarking and than I post the results here.
You should try it out with Result as a struct as well, to see if it goes faster.
@vcsjones: I made some improvements, but it's still an order of magnitude slower than even a simple loop. The reason isn't synchronization, but the massive method call overhead.
That "sux".. But .NET 4.5 now have a option to put methods inline: MethodImplOptions.AggressiveInlining. stackoverflow.com/questions/473782/inline-functions-in-c

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.