在 LINQ 查询中调用 .AsParallel() 的位置

Where to call .AsParallel() in a LINQ query

问题

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

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

或者像这样:

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

到底有什么区别?

我试过的

official documentation 来看,我几乎总是看到第一种方法,所以我认为这是可行的方法。
不过今天,我尝试 运行 对自己进行一些基准测试,结果令人惊讶。这是我 运行 的代码:

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}");

输出

call .AsParallel() before: 49
call .AsParallel() after: 4

因此,显然,尽管文档中说了什么,第二种方法要快得多。这里究竟发生了什么?

通常使用 AsParallel 的诀窍是确定并行性节省的成本是否超过并行处理的开销。

当条件易于评估时(例如您的条件),创建多个并行流并在最后收集其结果的开销大大超过了并行执行比较的好处。

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

举一个计算困难条件的例子,考虑一个决定一个数是否为质数的方法。在多核上并行执行此操作 CPU 将显示比非并行实现有显着改进。

不需要第二次使用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