Почему IQueryable в два раза быстрее, чем IEnumerable при использовании Linq To Objects

Я знаю разницу между IQueryable и IEnumerable, и я знаю, что коллекции поддерживаются Linq To Objects через интерфейс IEnumerable.

Что меня озадачивает, так это то, что запросы выполняются в два раза быстрее, когда коллекция преобразуется в IQueryable.

Пусть l— заполненный объект типа List, тогда запрос linq выполняется в два раза быстрее, если список lпреобразуется в IQueryableчерез l.AsQueryable().

Я написал простой тест с VS2010SP1 и .NET 4.0, который демонстрирует это:

private void Test()
{
  const int numTests = 1;
  const int size = 1000 * 1000;
  var l = new List<int>();
  var resTimesEnumerable = new List<long>();
  var resTimesQueryable = new List<long>();
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

  for ( int x=0; x<size; x++ )
  {
    l.Add( x );
  }

  Console.WriteLine( "Testdata size: {0} numbers", size );
  Console.WriteLine( "Testdata iterations: {0}", numTests );

  for ( int n = 0; n < numTests; n++ )
  {
    sw.Restart();
    var result = from i in l.AsEnumerable() where (i % 10) == 0 && (i % 3) != 0 select i;
    result.ToList();
    sw.Stop();
    resTimesEnumerable.Add( sw.ElapsedMilliseconds );
  }
  Console.WriteLine( "TestEnumerable" );
  Console.WriteLine( "  Min: {0}", Enumerable.Min( resTimesEnumerable ) );
  Console.WriteLine( "  Max: {0}", Enumerable.Max( resTimesEnumerable ) );
  Console.WriteLine( "  Avg: {0}", Enumerable.Average( resTimesEnumerable ) );

  for ( int n = 0; n < numTests; n++ )
  {
    sw.Restart();
    var result = from i in l.AsQueryable() where (i % 10) == 0 && (i % 3) != 0 select i;
    result.ToList();
    sw.Stop();
    resTimesQueryable.Add( sw.ElapsedMilliseconds );
  }
  Console.WriteLine( "TestQuerable" );
  Console.WriteLine( "  Min: {0}", Enumerable.Min( resTimesQueryable ) );
  Console.WriteLine( "  Max: {0}", Enumerable.Max( resTimesQueryable ) );
  Console.WriteLine( "  Avg: {0}", Enumerable.Average( resTimesQueryable ) );
}

Выполнение этого теста (с will numTests== 1 и 10) дает следующий результат:

Testdata size: 1000000 numbers
Testdata iterations: 1
TestEnumerable
  Min: 44
  Max: 44
  Avg: 44
TestQuerable
  Min: 37
  Max: 37
  Avg: 37

Testdata size: 1000000 numbers
Testdata iterations: 10
TestEnumerable
  Min: 22
  Max: 29
  Avg: 23,9
TestQuerable
  Min: 12
  Max: 22
  Avg: 13,9

Повторение тест, но переключение порядка (т.е. сначала измерение IQuerable, а затем IEnumerable) дает разные результаты!

Testdata size: 1000000 numbers
Testdata iterations: 1
TestQuerable
  Min: 75
  Max: 75
  Avg: 75
TestEnumerable
  Min: 25
  Max: 25
  Avg: 25

Testdata size: 1000000 numbers
Testdata iterations: 10
TestQuerable
  Min: 12
  Max: 28
  Avg: 14
TestEnumerable
  Min: 22
  Max: 26
  Avg: 23,4

Вот мои вопросы:

  1. Что я делаю не так?
  2. Почему IEnumerableработает быстрее, если тест выполняется после теста IQueryable?
  3. Почему IQueryableработает быстрее, если нет. тестовых прогонов увеличено?
  4. Есть ли штраф за использование IQueryableвместо IEnumerable?

Я задаю эти вопросы, потому что мне интересно, какой из них использовать для моего интерфейса репозитория.Прямо сейчас они запрашивают коллекции в памяти (Linq to Objects), но в будущем этот можетстать источником данных SQL. Если я создам классы репозитория сейчас с помощью IQueryable, я смогу безболезненно переключиться позже на Linq to SQL. Однако, если есть потеря производительности, то придерживаться IEnumerable, пока не задействован SQL, кажется более мудрым.

7
задан rbu 14 June 2012 в 12:21
поделиться