这可能并不奇怪,这只是我无法解释的东西。我只是尝试了一些并行编程,我想我会实现一个可以并行的最简单的例子;计算素数。
问题是:我似乎无法让4个逻辑处理器比单线程更快地计算素数。这是为什么? (我有一个i7-4500u)
这是我的代码(您可以将其基本粘贴到新的控制台应用程序中):
static void Main(string[] args)
{
var p = new Program();
p.Start();
}
private void Start()
{
StartMonitoringTask();
// This puts my cpu at 33%, but is really fast.
for (long current = 3; current < long.MaxValue; current++)
{
DeterminePrimeAndAddToTotal(current);
}
// This puts my cpu at 100%, but is way slower.
Parallel.For(3, long.MaxValue, (current) => DeterminePrimeAndAddToTotal(current));
}
private long lastPrime = 0;
private long totalFound = 0;
private void DeterminePrimeAndAddToTotal(long primeOrNot)
{
bool isPrime = true;
if (primeOrNot % 2 == 0) return; // even number? never prime.
long root = (long)Math.Sqrt((long)primeOrNot);
for (int i = 3; i <= root; i += 2) // check only uneven numbers.
{
if (primeOrNot % i == 0)
{
isPrime = false;
break;
}
}
if(isPrime)
{
totalFound++;
lastPrime = primeOrNot;
}
}
/// <summary>
/// This just starts a task to monitor the progress.
/// It's outputs the results to the console every second or so.
/// </summary>
private void StartMonitoringTask()
{
Task.Factory.StartNew(() =>
{
var sw = Stopwatch.StartNew();
while (true)
{
Task.Delay(1000).Wait();
Console.WriteLine(
"found: " + totalFound +
", last: " + lastPrime +
", " + (totalFound / (sw.ElapsedMilliseconds / 1000)) + " p/s");
}
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
更新(基于Frode的回答): Frode的答案似乎是合理的,所以为了证明这一点,我除了for和并行循环之外还添加了许多动作的Parallel.Invoke。像这样:
var numberOfActions = 20;
var actions = new List<Action>();
long chunkSize = int.MaxValue / numberOfActions;
for (int i = 0; i < numberOfActions; i++)
{
long from, to;
from = i == 0 ? 3 : (i * chunkSize);
to = (i + 1) * chunkSize;
actions.Add(new Action(() => { for (long j = from; j < to; j++) DeterminePrimeAndAddToTotal(j); }));
}
Parallel.Invoke(actions.ToArray());
这似乎和Parallel一样慢。但是。我错过了什么?
答案 0 :(得分:0)
为int64范围内的每个整数值旋转新线程的成本非常高。
将int64-range拆分为10个块,然后使用Parallel.Invoke(Action [] action)执行每个for-chunk。
然后你肯定会看到性能提升。
Parallel.Invoke(
()=> { for(int i=3;i<a;i++) DeterminePrimeAndAddToTotal(i); },
()=> { for(int i=a;i<b;i++) DeterminePrimeAndAddToTotal(i); },
()=> { for(int i=b;i<c;i++) DeterminePrimeAndAddToTotal(i); },
...
答案 1 :(得分:0)
我会忽略素数检测中的错误(2是素数,尽管它是偶数)。
你有两个基本问题:谬误的基准测试和竞争条件。
关于替补标记:你必须在现实条件下进行基准测试(或者你可以达到最接近的标准)......
在现实生活中,代码编译为&#34;发布&#34;而不是&#34;调试&#34;。
在现实生活中,代码运行时没有连接调试器。
当然,通常情况下,当您对某些代码进行基准测试时,您不希望包含JIT时间或其他一次性惩罚。
关于竞争条件,请阅读this Wiki article。有一个完美的例子说明代码中发生的事情(两个线程试图同时增加一个值)。然后有两种方法来解决这种竞争条件:
我将采用第二种解决方案,因为它的惩罚较少 为实现这一目标,我将使用PLINQ 您可以测试代码,但应该在不调试的情况下运行它。并行部分运行得更快(在只有2个内核的弱计算机上运行速度快两倍)。
private const int ITERATIONS = 1000000;
static void Main(string[] args)
{
var p = new Test();
p.Start();
}
private void Start()
{
Console.WriteLine();
DeterminePrimeAndAddToTotal(1);
var primes = Enumerable.Range(2, ITERATIONS)
.Select(num => new { Number = num, IsPrime = DeterminePrimeAndAddToTotal(num) })
.Where(value => value.IsPrime);
var parallelPrimes = Enumerable.Range(2, ITERATIONS)
.AsParallel()
.Select(num => new { Number = num, IsPrime = DeterminePrimeAndAddToTotal(num) })
.Where(value => value.IsPrime);
var watch = Stopwatch.StartNew();
Console.WriteLine(primes.Count());
watch.Stop();
var nonParallelTime = watch.ElapsedMilliseconds;
watch = Stopwatch.StartNew();
Console.WriteLine(parallelPrimes.Count());
watch.Stop();
var parallelTime = watch.ElapsedMilliseconds;
Console.WriteLine("parallel/non-parallel");
Console.WriteLine(string.Format("{0}/{1}", parallelTime, nonParallelTime));
}
private bool DeterminePrimeAndAddToTotal(long primeOrNot)
{
bool isPrime = primeOrNot <= 2 || (primeOrNot % 2 != 0);
long root = (long)Math.Sqrt((long)primeOrNot);
for (int i = 3; i <= root && isPrime; i += 2) // check only uneven numbers.
{
if (primeOrNot % i == 0)
{
isPrime = false;
}
}
return isPrime;
}
虽然这仍然是一个糟糕的基准......(它只是用于测试并行性,而不是用于检查任何有价值的信息);