Кратко въведение
System.Collections.Generric.List <T>е общ клас за колекция в .NET, който може да съхранява всякакъв тип данни благодарение на удобството и богатия си API, широко използван в ежедневието ни и може да се счита за най-използвания клас за колекция.
При писането на код често трябва да итерираме <T>колекция от списъци, за да получим елементите в нея за някаква бизнес обработка. Обикновено в комплекта няма много елементи и преминаването е много бързо. Но за обработка на големи данни, статистика, изчисления в реално време и <T>т.н., как бързо да се премине през списъка от десетки хиляди или стотици хиляди данни? Това е, което трябва да споделя с вас днес.
Режим на придвижване
Нека разгледаме производителността на различните методи за преминаване и да изградим следния бенчмарк за производителност, използвайки преминаване на събиране от различен порядък, за да видим производителността на различните методи. Откъсът от кода изглежда така:
Използвайте твърдението foreach
Foreach е най-често срещаният начин, по който преминаваме през колекции, това е синтактична захарна имплементация на итераторния модел и също така се използва като бенчмарк за това време.
Тъй като операторът foreach е синтактичен захар, компилаторът в крайна сметка извиква GetEnumerator() и MoveNext() с while цикъл за реализиране на функционалността. Компилираният код изглежда така:
Имплементацията на метода MoveNext() ще гарантира, че няма да има други нишки, които да променят колекцията в итерацията, и ако се случи промяна, ще се появи изключение InvalidOperationException, ще има проверка за препълване, за да се провери дали текущият индекс е легитимен, и също така трябва да се присвои съответният елемент на преброителя. Текущ атрибут,Така че всъщност представянето ѝ не е най-доброто, кодовият откъс изглежда така:
Нека разгледаме как се представя при различни размери на множества, а резултатите изглеждат така:
Вижда се, че при различни размери е необходима линейната връзка на растеж на времеемкия процес, дори ако преминава през 100w данни без никаква логика на обработката, отнема поне 1 секунди.
Използвайте метода ForEach за списък
Друг често използван начин е да използвате List.<T> ForEach() метод, който ви позволява да подадете делегат на действие<T>, който ще извиква делегата на действието, докато преминава през елемента<T>.
Това е <T>вътрешен метод за имплементация на List, така че може директно да достъпва частни масиви и да избягва проверки за препълване. Теоретично би трябвало да е бързо; Но в нашия сценарий има само един празен метод, който може да не се държи добре при напълно входящо извикване към foreach. По-долу е изходният код на метода ForEach, който показва, че няма проверка за препълване, но все пак запазва проверка на конкурентния номер на версията.
Освен това, тъй като е необходимо да се предаде делегат на метода ForEach, в кода на извикването той ще проверява дали обектът делегат в класа за генериране на затваряне е празен всеки път, а ако не, нов Action<T>(), както е показано по-долу:
Нека разгледаме как се сравнява с ключовата дума foreach по отношение на производителността. Следващото изображение показва резултатите от бенчмарка:
Съдейки по резултатите от теста, това е с 40% по-бавно от директното използване на ключовата дума foreach, изглежда, че ако не е необходимо, е по-добре да се използва директно foreach, така че има ли по-бърз начин?
за преминаване на примки
Връщайки се към най-стария ни начин – да използваме ключовата дума "for", за да преминем през колекцията. Това би трябвало да е най-ефективният метод за преминаване в момента, защото не изисква някакъв излишен код като предишните (въпреки че индексаторът също се проверява за предотвратяване на препълване), и очевидно не проверява номера на версията, така че в многопоточна среда колекцията се променя и няма да има изключение при използване на for. Тестовият код изглежда така:
Да видим как ще се получи.
Изглежда, че това е начинът, по който го очакваме.Използването на директния for цикъл е с 60% по-бързо от foreach, комплект, който преди отнемаше 1 секунда за преминаване, сега отнема само 400 милисекунди. Има ли по-бърз начин?
Използване на колекцииМаршал
След .NET5, dotnet общността внедри класа CollectionsMarshal, за да подобри производителността на операциите по събиране. Този клас реализира как да се достъпват нативни масиви от типове колекции (ако сте видели моя [. .NET оптимизация на производителността - Трябва да зададете началния размер за типове колекции, знаете, че основната реализация на много структури от данни са масивите. Така може да пропуска всякакви детекции и директно да достъпва оригиналния масив, който би трябвало да е най-бърз. Кодът изглежда така:
Виждате, че кодът, генериран от компилатора, е много ефективен.
Директният достъп до основния масив е много опасен, трябва да знаете какво правите с всеки ред код и да имате достатъчно тестове. Резултатите от бенчмарк са както следва:
УауИзползването на CollectionsMarshal е с 79% по-бързо от използването на Foreach, но това би трябвало да е причината за оптимизацията на JIT – няма голяма разлика между използването на Foreach и Span на цикъла на ключови думи.
резюме
Днес разговарях с вас за това как бързо да преминете през колекцията List, и в повечето случаи се препоръчва да се използва ключовата дума foreach, която има както проверка за препълване, така и многопоточна контрол на номер на версията, което може да ни улесни писането на правилния код.
Ако имате нужда от висока производителност и големи обеми данни, препоръчително е да използвате директно за и CollectionsMarshal.AsSpan, за да преминавате през колекцията.
Линк към изходния код на тази статия:
Входът към хиперлинк е видим.
Оригинален линк:Входът към хиперлинк е видим.
|