"Возвращение урожая" медленнее, чем возвращение "старой школы"?

Я делаю несколько тестов о возврате производительности возврата, и я обнаружил, что он медленнее, чем нормальный возврат.

Я тестировал переменные значения (int, double и т.д.) и некоторые типы ссылок (строка и т.д.)... И доходность возврата в обоих случаях была медленнее. Зачем использовать его?

Посмотрите мой пример:

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

Ответ 1

Рассмотрим разницу между 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 и т.д.).

Ответ 2

Во-первых, это удобная функция. Два, это позволяет вам делать ленивый возврат, а это означает, что он оценивается только при достижении значения. Это может быть бесценным в материалах, подобных запросу БД, или просто коллекции, которую вы не хотите полностью перебирать. В-третьих, это может быть быстрее в некоторых сценариях. Четыре, в чем разница? Вероятно, крошечная, поэтому микро оптимизация.

Ответ 3

Так как компилятор С# преобразует блоки итератора (yield return) в конечный автомат. В этом случае состояние машины очень дорого.

Вы можете прочитать больше здесь: http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

Ответ 4

Я использовал выход return, чтобы дать мне результаты по алгоритму. Каждый результат основан на предыдущем результате, но мне не нужны все они. Я использовал foreach с возвратом доходности, чтобы проверять каждый результат и прерывать цикл foreach, если я получаю результат, соответствующий моему требованию.

Алгоритм был достаточно сложным, поэтому я думаю, что для сохранения состояний между доходами доходности была достойная работа.

Я заметил, что он был на 3% -5% ниже, чем традиционный доход, но улучшение, которое я получаю, не нуждается в генерации всех результатов, намного больше, чем потеря производительности.

Ответ 5

.ToList(), если необходимо, чтобы действительно завершить отложенную итерацию IEnumerable, препятствует измерению основной части.

По крайней мере, важно инициализировать список известным размером:

const int listSize = 2000000; var tempList = новый List (listSize);

...

Список tempList = YieldReturnTest (listSize).ToList();

Примечание. Оба вызова заняли примерно одно и то же время на моей машине. Никакой разницы (Mono 4 на repl.it).