2015-11-05 2 views
3

Заинтересованные, есть подходы, имеет какие-либо отличия.
Итак, я создал два фрагмента.C# .First() vs [0]

Snippet A 
List<int> a = new List<int>(); 
a.Add(4); 
a.Add(6); 
int b = a.First(); 

и

Snippet B 
List<int> a = new List<int>(); 
a.Add(4); 
a.Add(6); 
int b = a[0]; 

В IL мы доверяем, так

Snippet A IL 
IL_0000: nop   
IL_0001: newobj  System.Collections.Generic.List<System.Int32>..ctor 
IL_0006: stloc.0  // a 
IL_0007: ldloc.0  // a 
IL_0008: ldc.i4.4  
IL_0009: callvirt System.Collections.Generic.List<System.Int32>.Add 
IL_000E: nop   
IL_000F: ldloc.0  // a 
IL_0010: ldc.i4.6  
IL_0011: callvirt System.Collections.Generic.List<System.Int32>.Add 
IL_0016: nop   
IL_0017: ldloc.0  // a 
IL_0018: call  System.Linq.Enumerable.First 
IL_001D: stloc.1  // b 
IL_001E: ret   

и

Snippet B IL 
IL_0000: nop   
IL_0001: newobj  System.Collections.Generic.List<System.Int32>..ctor 
IL_0006: stloc.0  // a 
IL_0007: ldloc.0  // a 
IL_0008: ldc.i4.4  
IL_0009: callvirt System.Collections.Generic.List<System.Int32>.Add 
IL_000E: nop   
IL_000F: ldloc.0  // a 
IL_0010: ldc.i4.6  
IL_0011: callvirt System.Collections.Generic.List<System.Int32>.Add 
IL_0016: nop   
IL_0017: ldloc.0  // a 
IL_0018: ldc.i4.0  
IL_0019: callvirt System.Collections.Generic.List<System.Int32>.get_Item 
IL_001E: stloc.1  // b 
IL_001F: ret 

Отрывок B производится одна команда более IL, но подойти быстрее в конец?

+0

Я лично предпочитаю использовать 'First()', поскольку он более читабельен. – Sweeper

+16

[Если у вас есть две лошади, и вы хотите знать, какая из них быстрее, чем расы ваших лошадей. Не пишите краткие описания лошадей, публикуйте их в Интернете и спрашивайте случайных незнакомцев, чтобы угадать, что быстрее!] (Http://ericlippert.com/2012/12/17/performance-rant/) – Rawling

+0

Хороший совет , Я найду способы расы их. –

ответ

3

Вы можете проверить это самостоятельно:

static void Main() 
    { 
     List<long> resultsFirst = new List<long>(); 
     List<long> resultsIndex = new List<long>(); 

     Stopwatch s = new Stopwatch(); 

     for (int z = 0; z < 100; z++) 
     { 
      List<int>[] lists = new List<int>[10000]; 

      int temp = 0; 

      for (int i = 0; i < lists.Length; i++) 
       lists[i] = new List<int>() { 4, 6 };     

      s.Restart(); 

      for (int i = 0; i < lists.Length; i++) 
       temp = lists[i].First(); 

      s.Stop(); 

      resultsFirst.Add(s.ElapsedTicks); 

      s.Restart(); 

      for (int i = 0; i < lists.Length; i++) 
       temp = lists[i][0]; 

      s.Stop(); 

      resultsIndex.Add(s.ElapsedTicks); 
     } 

     Console.WriteLine("LINQ First() : " + resultsFirst.Average()); 
     Console.WriteLine(Environment.NewLine); 
     Console.WriteLine("By index  : " + resultsIndex.Average()); 

     Console.ReadKey(); 
    } 

Выход в режиме выпуска:

LINQ First(): 367

По индексу: 84

Выходной в режиме отладки:

LINQ сперва(): 401

По индексу: 177

P.S.

Как я нашел в интернет исходный код для метода First является:

public static TSource First<TSource>(this IEnumerable<TSource> source) 
{ 
    IList<TSource> list = source as IList<TSource>; 
    if (list != null) 
    { 
     if (list.Count > 0) 
     { 
      return list[0]; 
     } 
    } 
    else 
    { 
     using (IEnumerator<TSource> enumerator = source.GetEnumerator()) 
     { 
      if (enumerator.MoveNext()) 
      { 
       return enumerator.Current; 
      } 
     } 
    } 
} 

Операция литья source as IList<TSource> весьма вероятно, причина, почему First() значительно медленнее.

+2

Не вызывает ли сначала общий метод 'First ' приведет к генерации кода во время выполнения? – astef

+0

Вы должны точно выполнить его в режиме освобождения;) –

+0

** ОНИ ЧУВСТВУЮТ БОЛЬ. ** –

0

Если вы задаете вопрос об асимптотической сложности, оба подхода: O(1), используйте любое из них.

Если вы спрашиваете о реальной скорости, нет ответа, поскольку он может отличаться от версии к версии, от одной машины к другой. Вы создаете IL, для другой версии .NET это не то же самое.

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

5

Метод Enumerable.First определяются как

public static TSource First<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) throw Error.ArgumentNull("source"); 
    IList<TSource> list = source as IList<TSource>; 
    if (list != null) { 
     if (list.Count > 0) return list[0]; 
    } 
    else { 
     using (IEnumerator<TSource> e = source.GetEnumerator()) { 
      if (e.MoveNext()) return e.Current; 
     } 
    } 
    throw Error.NoElements(); 
} 

Так что для List<T> он заканчивается, используя индексацию после проверки нулевой и гипса. Кажется, не так много, но когда я тестировал производительность, First был на 10 раз медленнее индексатора (for цикл, 10 000 000 итераций, выпуск сборки: первый - 100 мс, индекс - 10 мс).

+0

Это неверно для всех существующих версий фреймворка, и его можно изменить в будущем. – astef

+1

@astef Можете ли вы привести пример существующей версии фреймворка, для которой мой ответ неверен? Я также не могу представить никаких будущих изменений в рамках, которые сделают результаты значительно разными. 'Enumerable.First' уже оптимизирован для' IList '. Дополнительные шаги необходимы, я не думаю, что они могут быть оптимизированы JIT. Единственное, что изменило бы результаты, было бы быстрее. –

+0

Как минимум, все версии до 3.5.Даже если вы не можете себе представить, как может быть реализована реализация, это хороший тон, чтобы не делать предположений о будущем и всегда указывать точную версию декомпилированного кода. – astef

0

Испытано в LINQPad 5 с помощью следующего кода:

var sw = Stopwatch.StartNew(); 
for(int i = 0; i < 1000000000; i++) 
{ 
    List<int> a = new List<int>(); 
    a.Add(i); 
    a.Add(i+2); 
    int b = a.First();//[0] for B 
} 
sw.Stop(); 
Console.WriteLine(sw.ElapsedTicks); 

.First() дал 01: 04,021 и 0: 45,794 с оптимизацией. [0] дал 0: 44.288, 0: 27.968 с оптимизацией и лучшим кодом для меня, как я думаю.

Действительно, для меня [0] более читабельна, чем .First(), и обычно мне не нужны чеки, предоставленные им. Итак, в большинстве случаев я выберу [0]. Спасибо.

1

В общем, конкретных методов класса/интерфейс следует отдавать предпочтение более родовых реализации, потому что, ну, позже являются родовым и структура данных должны принять его особенности во внимание. Например, связанный список не должен предоставлять индекс, потому что он не может быть эффективно реализован. В идеале, каждая структура данных будет определять свой собственный метод с той же сигнатурой, что и соответствующий общий метод расширения, когда он может обеспечить лучшую реализацию, а компилятор будет обрабатывать это правильно. Его можно рассматривать как специализация и, к сожалению, не поддерживается очень хорошо, как в шаблонах C++. Реализация Enumerable.First является хорошим примером «обходного пути», а не решения - она ​​оптимизирует конкретный интерфейс BCL, но не может обрабатывать настраиваемую структуру данных (например, связанный список), которая может обеспечить такую ​​же информацию намного лучше, чем использование общая реализация. И это еще хуже для Enumerable.Last.

Для возобновления, если вы программируете против определенных классов/интерфейсов, используйте свои методы, когда это возможно. Если вы программируете против стандартных универсальных интерфейсов, ну, у вас нет других параметров в любом случае (кроме определения методов расширения, которые затеняют стандартные, но это обычно приводит к столкновениям).