“收益率回报”是否比“旧学校”回归慢?

时间:2013-08-09 11:45:09

标签: c# yield-return

我正在做一些关于收益率回归性能的测试,我发现它比正常回报慢。

我测试了值变量(int,double等)和一些引用类型(字符串等)......并且两种情况下的yield return都比较慢。为什么要用呢?

看看我的例子:

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

5 个答案:

答案 0 :(得分:12)

考虑File.ReadAllLinesFile.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); });

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

我使用文件显示我的示例,因为它更容易以这种方式演示概念,但对于内存中的集合也是如此。使用yield return将使用更少的内存,并且可能更快,特别是在调用只需要查看集合的一部分的方法时(Enumerable.AnyEnumerable.First等)。

答案 1 :(得分:2)

首先,这是一个方便的功能。二,它允许你做懒惰返回,这意味着它只在值被提取时进行评估。这在像数据库查询这样的东西中是非常宝贵的,或者只是一个你不想完全迭代的集合。三,在某些情况下可能会更快。四,有什么区别?可能很小,所以微观优化。

答案 2 :(得分:1)

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

您可以在此处阅读更多内容:http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

答案 3 :(得分:0)

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

算法很复杂,所以我认为在每次收益率回报之间保存状态需要做一些体面的工作。

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

答案 4 :(得分:0)

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

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

const int listSize = 2000000; var tempList = new List(listSize);

...

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

备注:这两个电话在我的机器上花了大约相同的时间..没有区别(repl.it上的Mono 4)。