Sanci
Sanci

Reputation: 119

AsParallel() or async/await

Let's suppose to do a method CountString that, given an array of strings and a int, returns the number of strings whose length is greater than that int. If I have to take advantage as far as possible of multicore hardware, is it enough to do:

public int CountString(string[] s, int i) 
{
  return s.AsParallel().Count( res => res.length > i);
}

or have i got to use somehow Tasks or even to mix task and PLinq??

It has to be considered only a simple example, i know this method not affects too much the hardware performance.

I'm wondering if it is better to do so, using AsParallel(), or if it is better to declare the method async and use the await (even if i don't know how) in the method body.

Upvotes: 3

Views: 4879

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149518

Edit:

As i see your actual question was a bit misleading, i'll try answering the intentional one. Specifically here, going with AsParallel would be a good approach, since there isn't actually anything you need to await. Since you're dealing with a collection, PLINQ or Paralle.ForEach are a good choice. Consider going with async-await when you have naturally async I/O bound operations. It is recommended not to wrap synchronous methods with async wrappers.


If you actually test your code, you might even be surprised to see that paralleling this piece of code actually has a negative performance cost on your method execution, depending on the size of the array you're iterating over.

Many times people forget that using threads actually has overhead, even when using threads out of the threadpool. You have to have a least amount of CPU intensive work for this to be worth taking the performance hit of your parallelization.

If your array is long enough, then using AsParallel should be suffice. There is no reason to be adding Task as PLINQ will take care of the parallalization just fine.

Ok, lets actually test this code. Im going to be iterating over a string[] filled with GUIDs. This is the code:

Main Method:

void Main()
{
    //JIT
    Test(0);

    Test(100);
    Test(1000);
    Test(10000);
    Test(1000000);
    Test(10000000);
}

public void Test(int itemAmount)
{
    string[] strings = Enumerable.Range(0, itemAmount).Select(i => Guid.NewGuid()
                                                      .ToString()).ToArray();

    var stopWatch = Stopwatch.StartNew();
    CountStringInParallel(strings, itemAmount);
    stopWatch.Stop();
    Console.WriteLine("Parallel Call: String amount: {0}, Time: {1}", 
                                                        itemAmount, stopWatch.Elapsed);
    
    stopWatch.Restart();
    CountStringSync(strings, itemAmount);
    stopWatch.Stop();
    Console.WriteLine("Synchronous Call: String amount: {0}, Time: {1}", 
                                                        itemAmount, stopWatch.Elapsed);
}

Parallel and Sync:

public int CountStringInParallel(string[] s, int i) 
{
    return s.AsParallel().Count( res => res.Length > i);
}

public int CountStringSync(string[] s, int i) 
{
    return s.Count(res => res.Length > i);
}

Results:

Parallel Call: String amount: 100, Time: 00:00:00.0000197

Synchronous Call: String amount: 100, Time: 00:00:00.0000026


Parallel Call: String amount: 1000, Time: 00:00:00.0000266

Synchronous Call: String amount: 1000, Time: 00:00:00.0000201


Parallel Call: String amount: 10000, Time: 00:00:00.0002060

Synchronous Call: String amount: 10000, Time: 00:00:00.0002003


Parallel Call: String amount: 1000000, Time: 00:00:00.0080492

Synchronous Call: String amount: 1000000, Time: 00:00:00.0135279


Parallel Call: String amount: 10000000, Time: 00:00:00.0744104

Synchronous Call: String amount: 10000000, Time: 00:00:00.1402474

You can see that up to 10,000 strings, the synchronous method is actually faster then the parallel.

Upvotes: 7

i3arnon
i3arnon

Reputation: 116518

As long as you don't use async-await AsParallel is all you need. There's no reason to use tasks directly as AsParallel does that for you under the hood.

It's important to remember that parallelism has an overhead which in your case would probably be bigger than the added value of parallelism. For parallelism to actually improve performance, you should be working on many items, and the work itself should be non-trivial.

If you do, however, need to use async-await AsParallel (and the rest of PLinq) doesn't fit as it predates the TAP. You would need to use Task.Run to parallelize the processing and Task.WhenAll to await for it all. Similar to this:

var tasks = items.Select(item => Task.Run(() => Process(item));
var results = await Task.WhenAll(tasks);
// process results

Upvotes: 1

Related Questions