我正在做一些关于收益率回归性能的测试,我发现它比正常回报慢。
我测试了值变量(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);
}
}
答案 0 :(得分:12)
考虑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); });
立即启动多个线程,这些线程在读取其他线路的同时进行处理。因此,读取时间与处理时间重叠,这意味着您的程序执行速度会更快。
我使用文件显示我的示例,因为它更容易以这种方式演示概念,但对于内存中的集合也是如此。使用yield return
将使用更少的内存,并且可能更快,特别是在调用只需要查看集合的一部分的方法时(Enumerable.Any
,Enumerable.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)。