2011-11-24 58 views
4

猜測...當i == 0時,該程序需要多長時間才能生成第一個輸出?它應該是即時的,對嗎?然後通過對yield的懶惰評估,它應該在此後快速連續產生輸出,對吧?用IEnumerable解釋這種奇怪的行爲/ yield

static void Main(string[] args) 
{ 
    Stopwatch stopwatch = Stopwatch.StartNew(); 
    int i = 0; 
    foreach (var item in massiveYieldStatement()) 
    { 
     if (i++ % 10000 == 0) 
      Console.WriteLine(stopwatch.ElapsedMilliseconds/1000); 
    } 
    Console.ReadKey(); 
} 

static IEnumerable<string> massiveYieldStatement() 
{ 
    yield return "a"; 
    yield return "a"; 

    .. repeat 200,000 times !! 

    yield return "a"; 
} 

但它不!它在4到21分鐘之間沒有輸出,然後很快完成 - 在一種情況下60ms以下!在這段時間內,使用了一個核心CPU的100%,內存使用量也在增長。在我遇到這種情況的實際場景中,Stackoverflow異常在第一次迭代發生之前甚至發生!我已經在Visual Studio中以調試模式並在命令提示符下以釋放模式嘗試過它。我已經在Windows 7 x64和Windows Server 2008 R2 x64上嘗試過了。

任何人都可以解釋這裏發生了什麼?它爲你複製嗎?

注意:這不是真正的代碼:真正的代碼產量聲明少得多,但更復雜。這只是最簡單的repro。

+3

老實說,200000'yield'語句?我很高興C#團隊沒有浪費他們的時間來優化那個...我從來不研究如何實現「yield」,但我想你的答案就在那裏。這與真實代碼有多接近(即「實際場景」)? – Kobi

+4

如果你知道60ms是否完成,那麼聽起來好像暫停發生在任何代碼執行之前,這讓我認爲JIT編譯器存在問題。它有可能試圖優化200,000例'switch'語句,導致一些病態行爲。是否有任何理由需要一個帶有如此多'yield'語句的迭代器? – Gabe

+1

執行你的'foreach'兩次。如果第二次真的很快,那可能是一次解釋你的觀察的JITting開銷。 – Ani

回答

4

該代碼生成的程序集大小僅爲幾MB。 yield return是一個特殊的野獸,因爲它看起來很簡單,但C#編譯器實際上生成一個類('狀態機')來實現巨量的YStateStatement方法。我很確定你正在等待JIT編譯器編譯該類的MoveNext()方法(你可以用ildasm來驗證:如果你嘗試打開MoveNext()方法,它也需要很多時間)。

+0

是的,ildasm也跌倒了。 –

2

問題不是產量,而是返回200K產量的函數(順便說一句,100K行已經減慢了我的VS)。它需要進行評估並生成一個新的州級,每次你從IEnumerable<string>.GetEnumerator返回時IEnumerator上的第一個MoveNext()

static IEnumerable<string> massiveYieldStatement() 
{ 
    for(int i = 0; i < 200000; ++i) 
     yield return "a"; 
} 

由於評估速度很快,因此運行速度很快。

相關問題