【问题标题】:How does a streaming operator differ from deferred execution?流式操作符与延迟执行有何不同?
【发布时间】:2012-04-05 21:08:51
【问题描述】:

在 LINQ 中,Where 是一个流式操作符。其中OrderByDescending 是一个非流式操作符。 AFAIK,流媒体运营商只收集下一个必要的项目。非流式操作符一次评估整个数据流。

我看不到定义 Streaming Operator 的相关性。对我来说,延迟执行是多余的。以我编写自定义扩展并使用 where 运算符和 orderby 使用它的示例为例。

public static class ExtensionStuff
{
    public static IEnumerable<int> Where(this IEnumerable<int> sequence, Func<int, bool> predicate)
    {
        foreach (int i in sequence)
        {
            if (predicate(i))
            {
                yield return i;
            }
        }
    }
}

    public static void Main()
    {
        TestLinq3();
    }

    private static void TestLinq3()
    {
        int[] items = { 1, 2, 3,4 };

        var selected = items.Where(i => i < 3)
                            .OrderByDescending(i => i);

        Write(selected);
    }



    private static void Write(IEnumerable<int> selected)
    {
        foreach(var i in selected)
            Console.WriteLine(i);
    }

在任何一种情况下,Where 都需要评估每个元素以确定哪些元素满足条件。它产生的事实似乎只是因为操作员获得了延迟执行而变得相关。

那么,Streaming Operators 的重要性是什么?

【问题讨论】:

  • items 中使用大约 20 亿个整数再试一次。
  • @cHao 或无限序列,或从开放网络流派生的序列。
  • 在某些情况下,Linq 无法延迟执行。某些操作需要 slurping IEnumerable。不是 Where(),一次一个。但肯定是 OrderBy,除非您知道 所有 集合项,否则无法对集合进行排序。我最喜欢的是 Enumerable.Reverse() ,它打破了 IEnumerable 规则。在 IList 上使用 O(n) 存储和 O(n) 执行进行了令人震惊的未优化。马虎。

标签: c# linq


【解决方案1】:

有两个方面:速度和内存。

当您使用.Take() 之类的方法仅使用原始结果集的一部分时,速度方面会变得更加明显。

// Consumes ten elements, yields 5 results.
Enumerable.Range(1, 1000000).Where(i => i % 2 == 0)
    .Take(5)
    .ToList();

// Consumes one million elements, yields 5 results.
Enumerable.Range(1, 1000000).Where(i => i % 2 == 0)
    .OrderByDescending(i => i)
    .Take(5)
    .ToList();

因为第一个示例在调用 Take 之前仅使用流式运算符,所以在 Take 停止评估之前,您最终只能产生 1 到 10 的值。此外,一次只能将一个值加载到内存中,因此内存占用非常小。

在第二个示例中,OrderByDescending 不是流式传输,因此在 Take 提取第一项时,必须将通过 Where 过滤器的整个结果放入内存中进行排序。这可能需要很长时间并产生很大的内存占用。

即使您没有使用Take,内存问题也可能很重要。例如:

// Puts half a million elements in memory, sorts, then outputs them.
var numbers = Enumerable.Range(1, 1000000).Where(i => i % 2 == 0)
    .OrderByDescending(i => i);
foreach(var number in numbers) Console.WriteLine(number);

// Puts one element in memory at a time.
var numbers = Enumerable.Range(1, 1000000).Where(i => i % 2 == 0);
foreach(var number in numbers) Console.WriteLine(number);

【讨论】:

    【解决方案2】:

    它产生的事实似乎只是因为 运算符获得延迟执行。

    那么,Streaming Operators 的重要性是什么?

    即您无法使用缓冲/非流式扩展方法处理无限序列 - 而您可以仅使用流式扩展方法“运行”这样的序列(直到您中止)。

    以这个方法为例:

    public IEnumerable<int> GetNumbers(int start)
    {
        int num = start;
    
        while(true)
        {
            yield return num;
            num++;
        }
    }
    

    你可以用Where就好了:

    foreach (var num in GetNumbers(0).Where(x => x % 2 == 0))
    {
        Console.WriteLine(num);
    }
    

    OrderBy() 在这种情况下不起作用,因为它必须在发出单个数字之前详尽地枚举结果。

    【讨论】:

    • Nitpick,但它是无限的,只是每个结果都不是唯一的。它最终会溢出,但它会换行,因此它可以无限次溢出。 (只要您不在选中的区块中。)如果您愿意,您可以使用while(true)yield return 4;
    【解决方案3】:

    只是为了明确;在您提到的情况下, where 流的事实没有任何优势,因为无论如何 orderby 都会吸收整个事情。然而,有时会使用流式传输的优势(其他答案/cmets 已给出示例),因此所有 LINQ 运算符尽其所能进行流式传输。 Orderby 尽可能多地流式传输,这恰好不是很多。流式传输非常有效。

    【讨论】:

    • 是否有 LINQ 运算符的流位于 WhereOrderBy 之间?
    • @ZevSpitz 我想这取决于你如何定义你的术语。 SkipWhile 可能符合条件,具体取决于您如何定义“流媒体”的数量。
    • MSDN 将流定义为能够一次解析一个结果(例如 Select),而非流需要所有值才能评估第一个值(例如 @987654326 @)。根据这个定义,为什么SkipWhile 的流媒体比Select 少?第一次迭代不需要所有值。
    • @ZevSpitz 您使用了严格的二进制定义,其中某些东西要么流化要么不流化,而不是模拟定义,其中某些东西可以流到给定程度。 SkipWhile 不需要消耗 all 的输入序列来返回第一个项目,但它需要消耗它的任意百分比,然后切换到消耗一个项目从产生的每个结果的输入序列中。再说一次,这就是为什么我说这取决于你如何定义你的术语;在这种情况下没有一个明确的定义。
    • 按照您的逻辑,Where 应该被认为是间歇性流式传输和非流式传输。但是确定值是否传递给下一个运算符 / foreach 的某些条件不会从根本上改变查询的行为;当需要传递给其他运算符时,可以精确地评估每个值。 OrderBy 非常不同——第一个结果需要对所有结果进行评估。
    猜你喜欢
    • 2021-04-08
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多