Производительность Linq: (ElementAt, Count) против (foreach) - PullRequest
4 голосов
/ 10 ноября 2011

Я перебираю IEnumerable в результате запроса Linq, используя (ElementAt, Count) и (foreach).К моему удивлению, разница в производительности составляет 25-30 раз!Почему это так?

IEnumerable<double> result =
     ... simple Linq query that joins two tables
     ... returns about 600 items

double total = 0;

// Method 1: iterate with Count and ElementAt
for( int i = 0; i < result.Count(); i++ )
{
    total += result.ElementAt(i);
}

// Method 2: iterate with foreach
foreach( double value in result )
{
    total += value;
}

Ответы [ 7 ]

10 голосов
/ 10 ноября 2011

Метод ElementAt() - это O (n), если только конкретный класс, который представляет IEnumerable, не оптимизирует его.Это означает, что каждый раз, когда вы вызываете его, он должен перебирать весь Enumerable, чтобы найти элемент в n.Не говоря уже о том, что, поскольку у вас есть i < result.Count() в условной части вашего цикла for, он должен перебирать все перечисляемое каждый раз, чтобы получить этот счет.

Во втором способе вы перебираете result ровно один раз.

6 голосов
/ 10 ноября 2011

Поскольку ElementAt выполняет итерацию по IEnumerable каждый раз, когда вы вызываете его.IEnumerable s не проиндексированы, поэтому ElementAt должно быть реализовано с использованием GetEnumerator().

Почему бы не сделать

total = result.Sum();
1 голос
/ 10 ноября 2011

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

1 голос
/ 10 ноября 2011

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

var theList = result.ToList();

for( int i = 0; i < result.Count; i++ )
{
    total += result[i];
}

Лучшее решение:

total = result.Sum();
0 голосов
/ 01 декабря 2011

Это все о понимании отложенного исполнения!Когда запрос выполняется несколько раз, время выполнения резко увеличивается.LINQ может быть быстрее, но вам действительно нужно сделать выбор в зависимости от того, как вы собираетесь использовать результаты запроса.

Взгляните на эту статью http://allthingscs.blogspot.com/2011/03/linq-performance.html. Она анализирует эту проблему.

0 голосов
/ 10 ноября 2011

Первое может быть эквивалентно:

double total = 0;

int i = 0;
while(true)
{
  int max = /*Database call to obtain COUNT(*) of join*/
  if(max > i)
    break;
  int j = 0;
  foreach(double value in result)
  {
    if(j++ == i)
    {
      total += value;
      break;
    }
  }
  ++i
}

Или это может быть даже эквивалентно:

double total = 0;

int i = 0;
while(true)
{
  int max = 0;
  foreach(double value in result)
    ++max;
  if(max > i)
    break;
  int j = 0;
  foreach(double value in result)
  {
    if(j++ == i)
    {
      total += value;
      break;
    }
  }
  ++i
}

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

С другой стороны, Count() может быть получено одним доступом к свойству, а ElementAt() может быть O (1), если они поддерживаются структурами, которые допускают такую ​​оптимизацию, и такая оптимизация действительно была доступна (например, для List<T>).

0 голосов
/ 10 ноября 2011

Если бы я рискнул догадаться, вызов result.Count () не был бы отложен и фактически попал в базу данных, а foreach - нет.Если вы перевернете заказ, вы можете получить противоположный результат.Также вы можете просто сделать total = result.Sum();

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