Parallel.Invoke 运行缓慢
Parallel.Invoke works slowly
我编写了代码 Methode #1 和 Methode #2 来比较性能。方法 #1 使用构造,方法 #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));
}
}
}
}
}
您可以使用的替代方法:
//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
第二种方法较慢的原因有很多。
checkDivisible
没有做足够的工作来证明并行化。并行化和同步化的开销可能大于任何好处。
- ConcurrentBag 是一个特殊用途的集合,它将数据存储在线程本地存储中,确保线程可以快速访问它写入的项目。在其他场景下,它实际上比其他并发收集慢。
- 所有对
checkDivisible
的调用都写入同一个集合,这成为一个热点。最好从每次调用中 return 一个简单的数组,最后在最后一步合并所有数组。
- 并发调用过多。 Parallel.Invoke 必须单独安排每个操作。
Parallel.For
或 Parallel.ForEach
另一方面知道所有调用都是相同的,因此他们可以根据处理器数量对数据进行分区,确保并行化开销最小。
第一步修改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
中的数据进行分区,并行处理分区,最后合并到一个列表中。
我编写了代码 Methode #1 和 Methode #2 来比较性能。方法 #1 使用构造,方法 #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));
}
}
}
}
}
您可以使用的替代方法:
//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
第二种方法较慢的原因有很多。
checkDivisible
没有做足够的工作来证明并行化。并行化和同步化的开销可能大于任何好处。- ConcurrentBag 是一个特殊用途的集合,它将数据存储在线程本地存储中,确保线程可以快速访问它写入的项目。在其他场景下,它实际上比其他并发收集慢。
- 所有对
checkDivisible
的调用都写入同一个集合,这成为一个热点。最好从每次调用中 return 一个简单的数组,最后在最后一步合并所有数组。 - 并发调用过多。 Parallel.Invoke 必须单独安排每个操作。
Parallel.For
或Parallel.ForEach
另一方面知道所有调用都是相同的,因此他们可以根据处理器数量对数据进行分区,确保并行化开销最小。
第一步修改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
中的数据进行分区,并行处理分区,最后合并到一个列表中。