5

I'm doing some tests about yield return perfomance, and I found that it is slower than normal return.

I tested value variables (int, double, etc.) and some references types (string, etc.)... And yield return were slower in both cases. Why use it then?

Check out my example:

public class YieldReturnTeste
{
    private static IEnumerable<string> YieldReturnTest(int limite)
    {
        for (int i = 0; i < limite; i++)
        {
            yield return i.ToString();
        }
    }

    private static IEnumerable<string> NormalReturnTest(int limite)
    {
        List<string> listaInteiros = new List<string>();

        for (int i = 0; i < limite; i++)
        {
            listaInteiros.Add(i.ToString());
        }
        return listaInteiros;
    }

    public static void executaTeste()
    {
        Stopwatch stopWatch = new Stopwatch();

        stopWatch.Start();

        List<string> minhaListaYield = YieldReturnTest(2000000).ToList();

        stopWatch.Stop();

        TimeSpan ts = stopWatch.Elapsed;


        string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",

        ts.Hours, ts.Minutes, ts.Seconds,

        ts.Milliseconds / 10);

        Console.WriteLine("Yield return: {0}", elapsedTime);

        //****

        stopWatch = new Stopwatch();

        stopWatch.Start();

        List<string> minhaListaNormal = NormalReturnTest(2000000).ToList();

        stopWatch.Stop();

        ts = stopWatch.Elapsed;


        elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",

        ts.Hours, ts.Minutes, ts.Seconds,

        ts.Milliseconds / 10);

        Console.WriteLine("Normal return: {0}", elapsedTime);
    }
}
4

5 回答 5

14

File.ReadAllLines考虑和之间的区别File.ReadLines

ReadAllLines将所有行加载到内存中并返回一个string[]. 如果文件很小,一切都很好。如果文件大于内存容量,则内存不足。

ReadLines,另一方面,用于yield return一次返回一行。有了它,您可以读取任何大小的文件。它不会将整个文件加载到内存中。

假设您想找到包含单词“foo”的第一行,然后退出。使用ReadAllLines,您必须将整个文件读入内存,即使“foo”出现在第一行也是如此。使用ReadLines,您只能阅读一行。哪个会更快?

这不是唯一的原因。考虑一个读取文件并处理每一行的程序。使用File.ReadAllLines,你最终得到:

string[] lines = File.ReadAllLines(filename);
for (int i = 0; i < lines.Length; ++i)
{
    // process line
}

该程序执行的时间等于读取文件的时间加上处理行的时间。想象一下,处理需要很长时间,以至于您想用多个线程来加速它。所以你做这样的事情:

lines = File.ReadAllLines(filename);
Parallel.Foreach(...);

但是阅读是单线程的。在主线程加载整个文件之前,您的多个线程无法启动。

但是,使用ReadLines,您可以执行以下操作:

Parallel.Foreach(File.ReadLines(filename), line => { ProcessLine(line); });

这会立即启动多个线程,这些线程在读取其他行的同时正在处理。因此读取时间与处理时间重叠,这意味着您的程序将执行得更快。

我使用文件来展示我的示例,因为这样更容易演示概念,但内存中的集合也是如此。Usingyield return将使用更少的内存并且可能更快,尤其是在调用只需要查看集合的一部分(Enumerable.Any、、Enumerable.First等)的方法时。

于 2013-08-09T12:29:24.000 回答
2

一方面,这是一项便利功能。第二,它允许你做延迟返回,这意味着它只在获取值时才被评估。这在数据库查询之类的东西中可能是无价的,或者只是一个您不想完全迭代的集合。三、在某些场景下可以更快。四、有什么区别?可能很小,所以微优化。

于 2013-08-09T11:49:06.630 回答
1

因为 C# 编译器将迭代器块 ( yield return) 转换为状态机。在这种情况下,状态机非常昂贵。

你可以在这里阅读更多:http: //csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

于 2013-08-09T12:14:49.740 回答
0

我使用 yield return 给我一个算法的结果。每个结果都是基于以前的结果,但我不需要所有这些。我使用 foreach 和 yield return 来检查每个结果,如果我得到满足我要求的结果,则打破 foreach 循环。

该算法相当复杂,所以我认为在每个收益回报之间保存状态涉及一些体面的工作。

我注意到它比传统回报慢了 3%-5%,但我从不需要生成所有结果的形式获得的改进比性能损失要大得多。

于 2013-11-07T20:33:58.423 回答
0

真正完成 IEnumerable 的其他延迟迭代所必需的.ToList()同时,阻碍了对核心部分的测量。

至少将列表初始化为已知大小很重要:

常量 int listSize=2000000; var tempList = new List(listSize);

...

列表 tempList = YieldReturnTest(listSize).ToList();

备注:这两个电话在我的机器上花费的时间差不多。没有区别(repl.it 上的 Mono 4)。

于 2017-05-21T20:38:27.070 回答