Короткий вступ
System.Collections.Generric.List <T>— це універсальний клас колекцій у .NET, який може зберігати будь-який тип даних завдяки своїй зручності та багатому API, що широко використовується в нашому повсякденному житті і вважається найпоширенішим класом колекцій.
Під час написання коду нам часто доводиться проходити <T>колекцію списків, щоб отримати елементи з неї для певної бізнес-обробки. Зазвичай у наборі не так багато елементів, і його дуже швидко пересувати. Але для обробки великих даних, статистики, обчислень у реальному часі тощо<T>, як швидко пройти список із десятків чи сотень тисяч даних? Ось чим я хочу поділитися з вами сьогодні.
Режим пересування
Давайте розглянемо продуктивність різних методів обходу і побудуємо наступний еталонний орієнтир, використовуючи обхід колекції різних порядків, щоб побачити ефективність різних методів. Фрагмент коду виглядає так:
Використовуйте твердження foreach
Foreach — це найпоширеніший спосіб, яким ми проходимо колекції, це синтаксисна цукрова реалізація ітераторного патерну, а також використовується як еталонна для цього часу.
Оскільки оператор foreach є синтаксичним цукрам, компілятор зрештою викликає GetEnumerator() і MoveNext() з циклом while для реалізації функціональності. Скомпільований код виглядає так:
Реалізація методу MoveNext() гарантує, що в ітерації не буде інших потоків, які змінюють колекцію, і якщо модифікація відбудеться, буде видано виключення InvalidOperationException, буде перевірка переповнення для перевірки легітимності поточного індексу, а також потрібно призначити відповідний елемент переписувачу. Поточний атрибут,Тож насправді його продуктивність не найкраща, фрагмент коду виглядає так:
Давайте подивимось, як він працює при різних розмірах наборів, і результати виглядають так:
Видно, що у випадку різних розмірів потрібна лінійна залежність зростання тривалого процесу, навіть якщо він проходить 100 Вт даних без жодної логіки обробки, це займає щонайменше 1 секунду.
Використовуйте метод ForEach для списку
Ще один поширений спосіб — використовувати List<T>. Метод ForEach(), який дозволяє передавати <T>делегата Дії, який викликає делегата Дії під час проходження елемента<T>.
Це <T>внутрішній метод реалізації List, що дозволяє безпосередньо отримувати доступ до приватних масивів і уникати перевірок переповнення. Теоретично це має бути швидко; Але в нашому сценарії є лише один порожній метод, який може погано працювати при повністю вбудованому виклику методу foreach. Нижче наведено вихідний код методу ForEach, який показує, що він не має перевірки переповнення, але все одно зберігає перевірку номера версії одночасно.
Крім того, оскільки необхідно передати делегата методу ForEach, у коді виклику він перевіряє, чи є об'єкт делегата в класі генерації закриття порожнім щоразу, а якщо ні — новий Action<T>(), як показано нижче:
Давайте подивимось, як це порівнюється з ключовим словом foreach за продуктивністю. Наступне зображення показує результати бенчмарку:
Судячи з результатів тесту, це на 40% повільніше, ніж пряме використання ключового слова foreach, і якщо це не потрібно, краще використовувати безпосередньо foreach, тож чи є якийсь швидший спосіб?
для об'їзду петлі
Повертаючись до нашого найдавнішого способу — використовувати ключове слово «for» для проходження колекції. Це має бути найкращий метод обходу на даний момент, оскільки він не потребує дублюючого коду, як попередні (хоча індексер також перевіряється для запобігання переповненням), і, очевидно, він не перевіряє номер версії, тому в багатопотоковому середовищі колекція змінюється, і при використанні для не буде жодного винятку. Тестовий код виглядає так:
Подивимось, що вийде.
Здається, саме так ми цього очікуємо.Використання прямого циклу For на 60% швидше, ніж Foreach, набір, який раніше проходив за 1 секунду, тепер займає лише 400 мілісекунд. То чи є швидший спосіб?
Використання колекційМаршал
Після .NET5 спільнота dotnet впровадила клас CollectionsMarshal для покращення ефективності операцій збору. Цей клас реалізує, як отримати доступ до рідних масивів типів колекцій (якщо ви бачили мій [. .NET Performance Optimization - You Should Set the Initial Size for Collection Types, ви знаєте, що основна реалізація багатьох структур даних — це масиви). Отже, він може пропускати всі види виявлень і безпосередньо отримувати доступ до оригінального масиву, який має бути найшвидшим. Код виглядає так:
Видно, що код, згенерований компілятором, дуже ефективний.
Прямий доступ до базового масиву дуже небезпечний, потрібно знати, що робити з кожним рядком коду, і мати достатньо тестів. Еталонні результати такі:
Вау,Використання CollectionsMarshal на 79% швидше, ніж foreach, але це має бути причиною оптимізації JIT, немає великої різниці між використанням foreach і для циклу ключових слів Span.
зведення
Сьогодні я говорив з вами про те, як швидко проходити колекцію List, і в більшості випадків рекомендується використовувати ключове слово foreach, яке має як перевірку переповнення, так і багатопотоковий контроль номера версії, що може полегшити написання правильного коду.
Якщо вам потрібні високопродуктивні та великі обсяги даних, рекомендується використовувати для та CollectionsMarshal.AsSpan безпосередньо для проходження колекції.
Посилання на вихідний код цієї статті:
Вхід за гіперпосиланням видно.
Оригінальне посилання:Вхід за гіперпосиланням видно.
|