Объясните это странное поведение с IEnumerable / yield - PullRequest
4 голосов
/ 24 ноября 2011

Подумайте ... сколько времени займет эта программа, чтобы произвести самый первый вывод, когда я == 0?Это должно быть мгновенно, верно?И через ленивую оценку 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 минуты, а затем быстро завершается - менее 60 мс в одном случае!В эти минуты используется 100% ЦП одного ядра, а использование памяти растет.В реальном сценарии, где я столкнулся с этим, исключение Stackoverflow выдается еще до того, как произойдет первая итерация!Я пробовал это в режиме отладки в Visual Studio и в режиме выпуска из командной строки.Я пробовал это на Windows 7 x64 и Windows Server 2008 R2 x64.

Может кто-нибудь объяснить, что здесь происходит?Это для вас воспроизводится?

ПРИМЕЧАНИЕ. Это не настоящий код: в реальном коде гораздо меньше операторов yield, но он намного сложнее.Это просто самое простое воспроизведение.

Ответы [ 2 ]

4 голосов
/ 24 ноября 2011

Сборка, созданная этим кодом, имеет размер несколько МБ. yield return - особый зверь в том смысле, что он выглядит обманчиво простым, но компилятор C # фактически генерирует класс («конечный автомат») для реализации метода massYieldStatement. Я уверен, что вы ждете, пока JIT-компилятор скомпилирует метод MoveNext () этого класса (вы можете проверить это с помощью ildasm: если вы попытаетесь открыть метод MoveNext (), это также займет много времени) ).

2 голосов
/ 24 ноября 2011

проблема не в доходности, а в функции, которая возвращает доходность 200К (между прочим, 100К линий уже замедлили мой VS).Он должен оцениваться и генерировать новый класс состояний каждый раз, когда вы делаете свой первый MoveNext() для IEnumerator, возвращаемого из IEnumerable<string>.GetEnumerator.

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

работает быстро, как и ожидалось, поскольку оценка быстрая.

...