在LINQ查询中何时调用.AsParallel()?

21

问题

在LINQ查询中,我可以正确地(编译器不会抱怨)这样调用.AsParallel():

(from l in list.AsParallel() where <some_clause> select l).ToList();

或者像这样:

(from l in list where <some_clause> select l).AsParallel().ToList();

这两种方法有什么具体的区别?

我尝试过的

官方文档来看,我几乎总是看到第一种方法被使用,因此我认为那是可行的方法。
然而今天,我尝试了一些基准测试,结果出人意料。这是我运行的代码:

var list = new List<int>();
var rand = new Random();
for (int i = 0; i < 100000; i++)
    list.Add(rand.Next());

var treshold= 1497234;

var sw = new Stopwatch();

sw.Restart();
var result = (from l in list.AsParallel() where l > treshold select l).ToList();
sw.Stop();

Console.WriteLine($"call .AsParallel() before: {sw.ElapsedMilliseconds}");

sw.Restart();
result = (from l in list where l > treshold select l).AsParallel().ToList();
sw.Stop();

Console.WriteLine($"call .AsParallel() after: {sw.ElapsedMilliseconds}");

输出

在调用.AsParallel()之前:49
在调用.AsParallel()之后:4

很明显,尽管文档中写的不一样,第二个方法要快得多。那么这里到底发生了什么?


你的机器是单核还是多核? - Vivek Nuna
@viveknuna 多核心 - user7061022
它每次都会给你不同的结果。 - Vivek Nuna
@viveknuna 是的,但是差异总是相同的。 - user7061022
2个回答

23

使用AsParallel的关键是决定并行节省的成本是否超过了并行执行任务的开销。

当条件容易评估时,例如您的情况下,制作多个并行流并在最后收集它们的结果的开销大大超过了并行比较的好处。

当条件具有计算强度时,尽早在AsParallel中进行调用可以显着加快速度,因为与在并行运行多个Where计算的好处相比,开销现在很小。

一个计算难度高的条件示例是判断一个数字是否为质数的方法。在多核CPU上并行执行此操作将比非并行实现显示出显著的改进。


谢谢您的回答,但我仍然不太明白这两种调用之间的区别。您是说当我在第二种方式中(在查询末尾)调用.AsParallel()时,我实际上并没有并行化任何内容吗? - user7061022
3
是的,到那时工作已经以顺序模式完成。 LINQ 需要做的就是将并行流的结果收集到一个单一的列表中。 - Sergey Kalinichenko

9

AsParallel的第二次使用是不必要的,它对some_clause没有影响。

另请参见下面的测试代码:

[TestMethod]
public void Test()
{
    var items = Enumerable.Range(0, 10);
    int sleepMs;
    for (int i = 0; i <= 4; i++)
    {
        sleepMs = i * 25;
        var elapsed1 = CalcDurationOfCalculation(() => items.AsParallel().Select(SomeClause).ToArray());
        var elapsed2 = CalcDurationOfCalculation(() => items.Select(SomeClause).AsParallel().ToArray());

        Trace.WriteLine($"{sleepMs}: T1={elapsed1} T2={elapsed2}");
    }

    long CalcDurationOfCalculation(Action calculation)
    {
        var watch = new Stopwatch();
        watch.Start();
        calculation();
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }

    int SomeClause(int value)
    {
        Thread.Sleep(sleepMs);
        return value * 2;
    }
}

及其输出:

0: T1=77 T2=11
25: T1=103 T2=272
50: T1=202 T2=509
75: T1=303 T2=758
100: T1=419 T2=1010

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接