Изпълнение на изброяващи стойности спрямо референтен тип

Warp Speed ​​от aalmada

Enumerable.Empty ()

В предишна статия се съсредоточих върху Enumerable.Empty (), тъй като това е най-простата възможна реализация на IEnumerable. Използвах моя собствена версия на кода, която е по-лесна за разбиране, в сравнение с тази, която в момента се намира в LINQ.

Скорошна заявка за сливане в хранилището dotnet / corefx я променя в нещо много близко до моята версия. Разликата е, че те използват сингъл от референтен тип, който реализира IEnumerable и IEnumerator. Това е възможно само защото Empty е неизменна, но много интересна.

Като се има предвид, че преди това всички изброители в BCL бяха типови стойности поради причини за производителност, бях любопитна за тази промяна.

Разглеждайки това, разбрах, че има много възможни реализации на Enumerable.Empty и реших да сравня няколко версии, които бих могъл да измисля.

EmptyStruct

EmptyEnumerableStruct е минималната имплементация, която може да бъде използвана в foreach, тъй като използва „типиране на патици“.

Той няма метод на Dispose (), така че опита / накрая блок няма да бъде използван от foreach.

Това е тип стойност, така че, когато се използва директно в foreach, няма да се получи бокс и всички обаждания от метода ще бъдат директни (няма виртуални обаждания). Той не може да бъде предаван на IEnumerable , тъй като не го прилага.

EmptyDisposableStruct и EmptyBoxedDisposableStruct

EmptyEnumerableDisposableStruct е тип стойност и реализира както IEnumerable , така и IEnumerator . Това принуждава и реализацията на IDisposable, което кара foreach да използва опита / най-накрая само за да извика празен метод Dispose ().

foreach ще използва публичните методи, които връщат типовете изрично (не интерфейси), така че да не се случват бокс или виртуални обаждания. Ако се предава на IEnumerable , вместо това ще се използват изричните изпълнения, където ще бъдат поставени в полето и обажданията на методи ще бъдат виртуални. И в двата случая методите ще върнат копия на екземплярите.

EmptyClass

Това е версия, много близка до тази, намираща се в клона „master“ на хранилището „dotnet / corefx“. Той е реализиран тук, тъй като все още не е пуснат.

EmptyEnumerableClass е референтен тип, който реализира IEnumerable , IEnumerator и IDisposable.

Той реализира единичен шаблон, така че за всеки използван тип T се създава само един екземпляр от класа. Всички обаждания на метод връщат препратка към този случай.

EmptyYieldBreak

Този метод използва реализацията, която се създава от компилатора, когато се използва ключовата дума за добив. В този случай това води до празно изброяване.

Това е референтен тип, който реализира IEnumerable и IEnumerator . Тя се различава от предишната реализация по това, че не е напълно оптимизирана за този сценарий и MoveNext () не просто връща невярно.

LinqEmpty

Това използва версията на Enumerable.Empty (), налична в момента в .NET Core.

EmptyList и EmptyBoxedList

Използва изброителя, върнат от празен списък (), понастоящем наличен в .NET Core, който е тип стойност. Също така е сравнителен показател, когато е предаван на IEnumerable , боксът на изброителя.

EmptyArray и EmptyBoxedArray

Използва изброителя, върнат от празен масив (), понастоящем наличен в .NET Core, който е референтен тип. Също така сравнителен показател е, когато е предаван на IEnumerable (Предполагам, че не предизвиква „бокс“, представянето е различно по причини, които не знам в момента).

Показатели

Създадох бенчмарк с помощта на BenchmarkDotNet, съдържащ всички тези реализации и тук имаме резултатите:

  1. Това, че опитате / най-накрая блокира, няма никаква разлика.
  2. EmptyDisposableStruct , който е тип стойност, е 33 пъти по-бърз от EmptyClass (новата версия на LINQ), но 2 пъти по-бавен, когато се предава на IEnumerable .
  3. Два случая на EmptyDisposableStruct са поставени в кутията (2 x 24B), когато са предадени на IEnumerable .
  4. EmptyClass (новата версия на LINQ) използва препратки към случаи, разпределени по-рано на купчината, така че да няма разпределения на купчина по време на метода за сравнение.
  5. LinqEmpty (текущата реализация на LINQ) се основава на EmptyBoxedArray, така че те имат същата производителност.
  6. EmptyClass (новата версия на LINQ) е с 30% по-бърз от LinqEmpty (текущата реализация на LINQ).
  7. Най-бавният е EmptyBoxedList ...

заключение

Въпреки че EmptyDisposableStruct е много по-бърз от EmptyClass (новата версия на LINQ), резултатът от Enumerable.Empty () обикновено се връща чрез методи, които извеждат IEnumerable . Това води до всички типови реализации, за да се получи „в полето“. Няма голяма полза за изричното предсказване на Изброено.Просто (). Използването на референтен тип сингъл изглежда страхотно обаждане от екипа .NET.

Надявам се да ви е харесало това пътуване толкова, колкото и аз. Това не е само за Enumerable.Empty (). Тези малки трикове могат да се използват на много други места, но, винаги измервайте ...