Изменение метода, который имеет «return» и «yield return» - PullRequest
6 голосов
/ 30 декабря 2011

Я знаю, что невозможно использовать return и yield return в одном методе.

Это код, который я хотел бы оптимизировать:

public IEnumerable<TItem> GetItems(int data)
{
    if (this.isSingleSet)
    {
        return this.singleSet; // is IEnumerable per-se
    }
    else
    {
        int index = this.GetSet(data);
        foreach(TKey key in this.keySets[index])
        {
            yield return this.items[key];
        }
    }
}

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

Я знаю, что есть два способа заставить этот метод работать:

  1. конвертировать yield return часть:

    ...
    else
    {
        int index = this.GetSet(data);
        return this.keySets[index].Select(key => this.items[key]);
    }
    
  2. конвертировать return часть:

    if (this.isSingleSet)
    {
        foreach(TItem item in this.singleSet)
        {
            yield return item;
        }
    }
    else ...
    

Но есть большая разница в скорости между ними. Использование только операторов return (другими словами, использование Select()) намного медленнее (например, в 6 раз) преобразование в yield return.

Вопрос

Есть ли другой способ, который приходит вам на ум, как написать этот метод? Есть ли у вас какие-либо другие сведения о предложениях, которые могут быть полезны для несоответствия производительности?

Дополнительная информация

Я измерял скорость двух методов с помощью секундомера вокруг for петли.

Stopwatch s = new Stopwatch();
s.Start();
for(int i = 0; i < 1000000; i++)
{
    GetItems(GetRandomData()).ToList();
}
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);

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

  1. Я запустил программу с одной версией метода, затем
  2. Закрыто
  3. Переписал метод и запустите его снова.

Сделал это несколько раз, чтобы увидеть достоверную разницу в производительности ...

Ответы [ 2 ]

15 голосов
/ 30 декабря 2011

Используйте две функции. Внешняя, вызываемая клиентами, функция выполняет все ленивые биты (например, проверку параметров), которые вы не хотите откладывать. Частный работник делает ленивые биты:

public IEnumerable<TItem> GetItems(int data) {
  if (this.isSingleSet) {
    return this.singleSet; // is IEnumerable per-se
  } else {
    return DoGetItems(data);
  }
}

private IEnumerable<TItem> DoGetItems(int data) {
  int index = this.GetSet(data);
  foreach(TKey key in this.keySets[index]) {
    yield return this.items[key];
  }
}
5 голосов
/ 30 декабря 2011

Реализация Select (с удаленной проверкой ошибок):

public static IEnumerable<R> Select<A, R>(
    this IEnumerable<A> sequence, 
    Func<A, R> projection)
{
    foreach(A item in sequence) 
        yield return projection(item);
}

Так что мне трудно поверить, что вы используете Select на очень медленнее, чем почти идентичный цикл foreach, который у вас уже есть. Это будет замедлено выполнением проверки ошибок (один раз) и созданием делегата (один раз), а также незначительными накладными расходами на косвенное обращение через делегат. Но петлевой механизм должен быть одинаковым.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...