Почему мой IEnumerable <String>использует возвращаемый результат медленнее для итерации, чем List <String> - PullRequest
0 голосов
/ 24 декабря 2008

Я тестировал оператор yield return с некоторым кодом, который писал. У меня есть два метода:

public static IEnumerable<String> MyYieldCollection {
        get 
        {
            wrapper.RunCommand("Fetch First From Water_Mains");
            for (int row = 0; row < tabinfo.GetNumberOfRows() ; row++) //GetNumberOfRows
                                                                      //will return 1000+ most of the time.
            {
                yield return wrapper.Evaluate("Water_Mains.col1");
                wrapper.RunCommand("Fetch Next From Water_Mains");
             }
        }
    }

и

public static List<String> MyListCollection
    {
        get
        {
            List<String> innerlist = new List<String>();

            wrapper.RunCommand("Fetch First From Water_Mains");
            for (int row = 0; row < tabinfo.GetNumberOfRows(); row++)
            {
                innerlist.Add(wrapper.Evaluate("Water_Mains.col1"));
                wrapper.RunCommand("Fetch Next From Water_Mains");
            }
            return innerlist;
        }
    }

тогда я использую цикл foreach для каждой коллекции:

        foreach (var item in MyYieldCollection) //Same thing for MyListCollection.
        {
            Console.WriteLine(item);
        }

Забавно, что по какой-то причине мне кажется, что я могу зацикливаться и распечатывать полную MyListCollection быстрее, чем MyYieldCollection.

Результаты:

  • MyYieldCollection -> 2062
  • MyListCollection -> 1847

Я действительно не вижу причины для этого, я что-то упустил или это нормально?

Ответы [ 4 ]

4 голосов
/ 24 декабря 2008

Как вы справились со временем? Вы в отладчике? В режиме отладки? Похоже, вы используете DataTable, поэтому я использовал ваш код в качестве шаблона для испытательного стенда (каждый раз создавая 1000 строк) и использовал жгут, как показано ниже, в режиме выпуска в командной строке ; результаты были следующими (число в скобках является проверкой, чтобы увидеть, что они оба сделали одну и ту же работу):

Yield: 2000 (5000000)
List: 2100 (5000000)

Испытательный жгут:

static  void Main()
{
    GC.Collect(GC.MaxGeneration,GCCollectionMode.Forced);
    int count1 = 0;
    var watch1 = Stopwatch.StartNew();        
    for(int i = 0 ; i < 5000 ; i++) {
        foreach (var row in MyYieldCollection)
        {
            count1++;
        }
    }
    watch1.Stop();

    GC.Collect(GC.MaxGeneration,GCCollectionMode.Forced);
    int count2 = 0;
    var watch2 = Stopwatch.StartNew();
    for (int i = 0; i < 5000; i++)
    {
        foreach (var row in MyListCollection)
        {
            count2++;
        }
    }
    watch1.Stop();

    Console.WriteLine("Yield: {0} ({1})", watch1.ElapsedMilliseconds, count1);
    Console.WriteLine("List: {0} ({1})", watch2.ElapsedMilliseconds, count2);
}

(обратите внимание, что обычно вы не должны использовать GC.Collect, но в нем есть выравнивание поля для тестов производительности)

Единственное другое изменение, которое я сделал, было в цикле for, чтобы избежать повторения:

int rows = tabinfo.Rows.Count;
for (int row = 0; row < rows; row++) {...}

Так что я не воспроизводлю ваши цифры ...

1 голос
/ 24 декабря 2008

Что произойдет, если одна итерация вашего цикла стоит дорого, и вам нужно всего лишь перебрать несколько элементов в вашей коллекции?

С доходом нужно платить только за то, что вы получаете;)

public IEnumerable<int> YieldInts()
{
    for (int i = 0; i < 1000; i++)
    {
        Thread.Sleep(1000) // or do some other work
        yield return i;
    }
}

public void Main()
{
    foreach(int i in YieldInts())
    {
        Console.WriteLine(i);
        if(i == 42)
        {
            break;
        }
    }
}
0 голосов
/ 24 декабря 2008

Я предполагаю, что JIT может лучше оптимизировать цикл for в версии, которая возвращает список. В версии, возвращающей IEnumerable, переменная строки, используемая в цикле for, теперь фактически является членом сгенерированного класса, а не переменной, локальной только для метода.

Разница в скорости составляет всего около 10%, поэтому, если это не критичный для производительности код, я бы об этом не беспокоился.

0 голосов
/ 24 декабря 2008

Насколько я понимаю, "возвращение урожая" будет продолжаться до тех пор, пока не будет выполнено наше задание, и функция / свойство завершится, возвращая заполненный IEnumarable. Другими словами, вместо того, чтобы вызывать функцию для каждого элемента в цикле foreach, она вызывается один раз и перед выполнением чего-либо внутри цикла foreach.

Это может быть тип возвращаемых коллекций. Возможно, список можно повторить быстрее, чем любая структура данных, которую представляет IEnumerable.

...