使用PLINQ并不总是明智的选择
PLINQ并不能总是提升速度,而且它始终会增加额外的开销。
从CPU指令的角度来看,无论你需要执行多少工作(称之为 X),你最终都要执行超过 X 的指令。PLINQ会进行大量的额外工作,比如启动线程、将工作委托给它们,并将结果转换为你能够处理的形式。
这样做的好处是可以让多个CPU/Core同时工作。有时它可以提高速度。但当你所需执行的CPU工作相对于开销很小时,它反而会变慢。
运行你的代码时,我得到以下输出:
顺序执行 2 毫秒
并行执行 40 毫秒
此外,我还看到 PLINQ 代码创建了八个工作线程。这八个线程代表了大量的开销,但实际上只执行了 2 毫秒的计算。如果你再次运行并行执行基准测试,就能感受到它所产生的开销。工作线程会一直存在。以下是第二次运行的输出:
顺序执行 2 毫秒
并行 #1 执行 40 毫秒
并行 #2 执行 3 毫秒
第二次运行速度更快了,但仍然比不进行任何操作慢。因为即使工作线程已经创建,PLINQ仍然需要分配操作、将结果返回到可访问的格式中。
你需要执行的工作越多,额外开销对性能影响就越小。在这个例子中,我用名为IsValid
的静态函数替换了你的Where lambda,并且计算了500次%2。
static bool IsValid(int input)
{
int result=0;
for(int i =0;i<500;i++)
result = input%2;
return result == 0;
}
现在-我的执行时间如下:
顺序执行 36 毫秒
并行 #1 执行 47 毫秒
并行 #2 执行 9 毫秒
您可以看到,在第一次执行上,PLINQ 仍然比较慢,但在第二次执行上显着更快。如果通过将循环从 500 增加到 5000 来增加 CPU 工作(在我的计算机上),PLINQ 轻松获胜。
TL;DR- 您正在做正确的事情;只是您没有做足够的工作使 PLINQ 成为更快的选择。
这是我所做的全部源代码:
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
int[] vals = Enumerable.Range(0, Int16.MaxValue).ToArray();
sw.Start();
int[] x1 = vals.Where(IsValid).ToArray();
sw.Stop();
Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);
sw.Restart();
int[] x2 = vals.AsParallel().Where(IsValid).ToArray();
sw.Stop();
Console.WriteLine("Parallel #1 Execution {0} milliseconds", sw.ElapsedMilliseconds);
sw.Restart();
int[] x3 = vals.AsParallel().Where(IsValid).ToArray();
sw.Stop();
Console.WriteLine("Parallel #2 Execution {0} milliseconds", sw.ElapsedMilliseconds);
Console.Read();
}
static bool IsValid(int input)
{
int result=0;
for(int i =0;i<5000;i++)
result = input%2;
return result == 0;
}