Parallel.Invoke工作缓慢

时间:2015-03-11 10:08:21

标签: c# parallel-processing task

我为代码性能编写了代码Methode#1和Methode#2。 Methode#1使用construnction,而Methode#2使用Parallel.Invoke。在第二种情况下工作非常缓慢。我无法理解为什么会这样?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Mpir.NET;
using System.Runtime.Serialization;
using System.Diagnostics;

namespace ConsoleApplication4
{
    class Program
    {
        public class numbers
        {
            public numbers(mpz_t p, mpz_t q)
            {
                this.q = q;
                this.p = p;
            }
            mpz_t q;
            mpz_t p;
        };

        static void Main(string[] args)
        {
            Int32 arraySize = 400;
            ConcurrentBag<numbers> pairsCB = new ConcurrentBag<numbers>();
            ConcurrentBag<Action> cbTasks = new ConcurrentBag<Action>();
            HashSet<numbers> uniqueCB = new HashSet<numbers>(pairsCB.ToArray());

            mpz_t[] numbersArray = new mpz_t[arraySize];

            for (Int32 i = 0; i < arraySize; i++)
            {
                numbersArray[i] = i*i+i+1; 
            }


            //Methode #1 
            Stopwatch stopwatch1 = new Stopwatch();
            stopwatch1.Start();

            for (Int32 j = 0; j < arraySize; j++)
            {
                checkDivisible(numbersArray[j], pairsCB);
            }

            uniqueCB = new HashSet<numbers>(pairsCB.ToArray());
            stopwatch1.Stop();
            Console.WriteLine("Methode Count Unique Pairs  Count:{0}\tTime elapsed:{1}", uniqueCB.Count(), stopwatch1.Elapsed);

            //Methode #2 
            Stopwatch stopwatch2 = new Stopwatch();
            stopwatch2.Start();
            pairsCB = new ConcurrentBag<numbers>();

            for(Int32 j = 0; j < arraySize; j++)
            {
                mpz_t value = numbersArray[j];
                cbTasks.Add(new Action(() => checkDivisible(value, pairsCB)));
            }

            Action[] tasks =  cbTasks.ToArray();

            Parallel.Invoke(tasks);

            stopwatch2.Stop();
            Console.WriteLine("Methode Count Unique Pairs  Count:{0}\tTime elapsed:{1}", uniqueCB.Count(), stopwatch2.Elapsed);


            Console.ReadKey();
        }

        private static void checkDivisible(mpz_t n, ConcurrentBag<numbers>  pq)
        {
            mpz_t p = 1; 
            mpz_t q = 1;    

            for (Int32 i = 2; i < n; i++)
            {
                if (n % i == 0)
                {
                    q = i;
                    p = n / i;
                    pq.Add(new numbers(p, q));
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

您可以使用替代方案:

//Methode #2 
Stopwatch stopwatch2 = new Stopwatch();
stopwatch2.Start();
pairsCB = new ConcurrentBag<numbers>();

Parallel.For(0, arraySize, (index) => 
{
     checkDivisible(numbersArray[index], pairsCB);
});

stopwatch2.Stop();

使用arraySize = 1000输出

Methode Count Unique Pairs  Count:3878  Time elapsed:00:00:01.5671572
Methode Count Unique Pairs  Count:3878  Time elapsed:00:00:00.7917211

答案 1 :(得分:2)

第二种方法速度慢的原因有很多。

  1. checkDivisible没有做足够的工作来证明并行化的合理性。并行化和同步的开销可能比任何好处都大。
  2. ConcurrentBag是一个专用的集合,它将数据存储在线程本地存储中,确保线程可以快速访问它编写的项目。它实际上比其他场景中的其他并发集合慢。
  3. checkDivisible的所有调用都会写入同一个集合,该集合将成为热点。最好从每次调用返回一个简单的数组,最后在最后一步中合并所有数组。
  4. 并发呼叫太多。 Parallel.Invoke必须单独安排每个Action。另一方面,Parallel.ForParallel.ForEach知道所有呼叫都是相同的,因此他们可以根据处理器的数量对数据进行分区,从而确保并行化开销最小化。
  5. 第一步是修改checkDivisible

        private static List<number> checkDivisible(mpz_t n)
        {
            mpz_t p = 1; 
            mpz_t q = 1;    
    
            List<number> nums=new List<numbers>();
    
            for (Int32 i = 2; i < n; i++)
            {
                if (n % i == 0)
                {
                    q = i;
                    p = n / i;
                    nums.Add(new numbers(p, q));
                }
            }
            return numbers;
        }
    

    我更喜欢迭代器方法,因为它避免了创建List只是为了收集结果。

    然后你可以使用Parallel.For:

    var results=new ConcurrentQueue<IList<numbers>>();
    
    Parallel.For(0, arraySize, (index) => 
    {
         var local=checkDivisible(numbersArray[index]);
         results.Add(local);
    });
    
    var final=results.SelectMany(r=>r).ToList();
    

    最后一步可以是您想要的任何内容,以确保结果是您想要的形式,例如。使用ToDictionary或ToLookup根据键合并结果。

    另一种选择是使用PLINQ以更简洁的方式做同样的事情。将checkDivision更改为迭代器:

        private static IEnumerable<number> checkDivisible(mpz_t n)
        {
            mpz_t p = 1; 
            mpz_t q = 1;    
    
            for (Int32 i = 2; i < n; i++)
            {
                if (n % i == 0)
                {
                    q = i;
                    p = n / i;
                    yield return new numbers(p, q);
                }
            }
        }
    

    你可以写:

    var results= (from n in numbersArray.AsParallel()
                 from number in checkDivisible(n)
                 select n).ToList();
    

    就像Parallel.For一样,PLINQ将根据计算机上的核心数量对numbersArray中的数据进行分区,并行处理分区,最后将它们合并到一个列表中。