如何确定并行foreach循环是否会比foreach循环具有更好的性能?

时间:2016-11-05 21:22:56

标签: c# .net asynchronous foreach parallel-processing

我只是在.NET Fiddle中进行了一个简单的测试,即对100个长度为1000的随机整数数组进行排序,看看使用Paralell.ForEach循环执行此操作是否比普通的旧foreach循环更快。

这是我的代码(我把它快速放在一起,所以请忽略代码的重复和整体不良外观)

using System;
using System.Net;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

public class Program
{
    public static int[] RandomArray(int minval, int maxval, int arrsize)
    {
        Random randNum = new Random();
        int[] rand = Enumerable
            .Repeat(0, arrsize)
            .Select(i => randNum.Next(minval, maxval))
            .ToArray(); 
        return rand;
    }

    public static void SortOneThousandArraysSync()
    {
        var arrs = new List<int[]>(100);
        for(int i = 0; i < 100; ++i)
            arrs.Add(RandomArray(Int32.MinValue,Int32.MaxValue,1000));
        Parallel.ForEach(arrs, (arr) =>
        {
            Array.Sort(arr);
        });
    }

    public static void SortOneThousandArraysAsync()
    {
        var arrs = new List<int[]>(100);
        for(int i = 0; i < 100; ++i)
            arrs.Add(RandomArray(Int32.MinValue,Int32.MaxValue,1000));
        foreach(var arr in arrs)
        {
            Array.Sort(arr);
        };      
    }

    public static void Main()
    {
        var start = DateTime.Now;
        SortOneThousandArraysSync();
        var end = DateTime.Now;
        Console.WriteLine("t1 = " + (end - start).ToString());
        start = DateTime.Now;
        SortOneThousandArraysAsync();
        end = DateTime.Now;
        Console.WriteLine("t2 = " + (end - start).ToString());
    }
}

以下是点击Run两次后的结果:

t1 = 00:00:00.0156244
t2 = 00:00:00.0156243

...

t1 = 00:00:00.0467854
t2 = 00:00:00.0156246

...

所以,有时候它会更快,有时也差不多。

可能的解释:

  • 对于同步一个,随机数组是“更多未分类”而在我运行的第二个测试中是异步数据
  • 它与.NET Fiddle上运行的进程有关。在第一种情况下,并行操作基本上像非并行操作一样,因为我的小提琴没有任何线程可以接管。 (或类似的东西)

思想?

3 个答案:

答案 0 :(得分:2)

如果循环中的代码需要花费大量时间来执行,那么您应该只使用Parallel.ForEach()。在这种情况下,创建多个线程,对数组进行排序,然后将结果组合到一个线程上比在单个线程上简单地对其进行排序需要更多的时间。例如,以下代码段中的Parallel.ForEach()执行时间比正常的ForEach循环少:

public static void Main(string[] args)
{
    var numbers = Enumerable.Range(1, 10000);

    Parallel.ForEach(numbers, n => Factorial(n));

    foreach (var number in numbers)
    {
        Factorial(number);
    }
}

private static int Factorial(int number)
{
    if (number == 1 || number == 0)
        return 1;

    return number * Factorial(number - 1);
}

但是,如果我将var numbers = Enumerable.Range(1, 10000);更改为var numbers = Enumerable.Range(1, 1000);,则ForEach循环会比Parallel.ForEach()更快。

答案 1 :(得分:1)

使用小任务(不需要花费大量时间执行)时,请查看Partitioner类;在你的情况下:

public static void SortOneThousandArraysAsyncWithPart() {
  var arrs = new List<int[]>(100);

  for (int i = 0; i < 100; ++i)
    arrs.Add(RandomArray(Int32.MinValue, Int32.MaxValue, 1000));

  // Let's spread the tasks between threads manually with a help of Partitioner.
  // We don't want task stealing and other optimizations: just split the
  // list between 8 (on my workstation) threads and run them
  Parallel.ForEach(Partitioner.Create(0, 100), part => {
    for (int i = part.Item1; i < part.Item2; ++i)
      Array.Sort(arrs[i]);
  });
}

我得到以下结果(i7 3.2GHz 4核HT,.Net 4.6 IA-64) - 平均100次运行:

0.0081 Async (foreach)
0.0119 Parallel.ForEach
0.0084 Parallel.ForEach + Partitioner

正如您所见,foreach仍位居榜首,但Parallel.ForEach + Partitioner非常接近获胜者

答案 2 :(得分:0)

检查算法的性能是一项棘手的工作,小规模的性能很容易受到代码外部各种因素的影响。请参阅my answer to an almost-duplicate question here以获得深入的解释,以及一些您可以调整以更好地衡量算法性能的基准测试模板的链接。