[英]Is 'yield return' slower than “old school” 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);
}
}
考慮File.ReadAllLines
和File.ReadLines
之間的區別。
ReadAllLines
將所有行加載到內存中並返回一個string[]
。 如果文件很小,一切都很好。 如果文件大於適合內存的文件,則內存不足。
另一方面, ReadLines
使用yield return
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
等)。
首先,它是一個方便的功能。 二,它允許你做懶惰返回,這意味着它只在值被提取時進行評估。 這在像數據庫查詢這樣的東西中是非常寶貴的,或者只是一個你不想完全迭代的集合。 三,在某些情況下可能會更快。 四,有什么區別? 可能很小,所以微觀優化。
因為C#編譯器將迭代器塊( yield return
)轉換為狀態機。 在這種情況下,狀態機非常昂貴。
你可以在這里閱讀更多內容: http : //csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx
我使用yield return來給出算法的結果。 每個結果都基於以前的結果,但我並不需要所有結果。 我使用帶有yield return的foreach來檢查每個結果,如果我得到滿足我要求的結果,則打破foreach循環。
算法很復雜,所以我認為在每個收益率回報之間保存狀態需要一些體面的工作。
我注意到它比傳統回報慢了3%-5%,但我得到的改進不需要產生所有結果,遠遠大於性能損失。
.ToList()
雖然是真正完成IEnumerable的延遲迭代所必需的, .ToList()
阻礙了測量核心部分。
至少將列表初始化為已知大小非常重要:
const int listSize = 2000000; var tempList = new List(listSize);
...
列表tempList = YieldReturnTest(listSize).ToList();
備注:這兩個電話在我的機器上花了大約相同的時間..沒有區別(repl.it上的Mono 4)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.