Как избежать ошибок с массивами в C # .NET

Эффективное использование базовых типов в C #

Простые, но умные стратегии, которые вы можете использовать для оптимизации кода C #, понимая, как массивы ведут себя в среде выполнения .NET, и выбирая правильные функции для работы при работе с массивами в горячих путях приложения.

Избегайте выделения пустых массивов

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

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

В конечном итоге это приведет к дополнительной работе по сборке мусора:

Когда выделяется пустой массив, в нем нельзя хранить никакие элементы. Так зачем вообще вообще нужно выделять массивы?

Альтернатива: Array.Empty ‹T› ()

Array.Empty ‹T› () использует статическое свойство, доступное только для чтения, для создания только одного экземпляра массива в течение всего жизненного цикла приложения.

Эта стратегия позволяет избежать наличия нескольких экземпляров пустых массивов одного и того же типа, сэкономить память и выполнить дополнительную работу по сборке ненужных объектов.

Вот его синтаксис:

Поскольку теперь мы имеем дело только с одним единственным экземпляром, мы можем достичь нулевого измерения распределения при сравнении Array.Empty<T> с new [] для любого количества вызовов:

Итог: Избегайте размещения новых пустых массивов. Вместо этого используйте Array.Empty ‹T› ().

Используйте ArrayPools для больших массивов

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

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

ArrayPools предоставляет способ предотвратить эти паузы, используя повторно используемые массивы указанного универсального типа ‹T›. Класс ArrayPool предоставляет нам метод Rent, который извлекает из этого пула массив с указанной минимальной длиной, готовый для использования вызывающей стороной.

Основная задача ArrayPool - избежать давления на сборщик мусора за счет уменьшения количества выделений и освобождений больших массивов приложением.

Вот его синтаксис:

  • Чрезвычайно важно вернуть взятый напрокат объект в его бассейн и больше не использовать его. Невозможность возврата массива в пул может снизить производительность вашего приложения, а использование массива, уже возвращенного в его пул, может привести к ошибкам и сбоям во время выполнения.

Итог: избегайте выделения и освобождения больших долгоживущих объектов и используйте их повторно.

Методы массивов и их аналоги в LINQ

Одна из ключевых вещей, которую вы можете заметить, читая Рекомендации по использованию компилятора .NET Roslyn, - это следующее утверждение:

DO avoid allocations in compiler hot paths:
    - DO avoid LINQ

Тем не менее, нередко можно увидеть, как методы LINQ красиво разбросаны по некоторым базам кода.

* И, честно говоря, я даже не уверен, что аргумент более читабельный действителен в некоторых случаях, но это обсуждение для другой статьи! 😉

Вернемся к массивам ... Вам могут не понадобиться методы пространства имен LINQ, если у вас под рукой есть конкретный экземпляр массива.

Вы можете протестировать его. Но в целом использование методов расширения LINQ на горячих путях имеет тенденцию приводить к неэффективности кода при высоких нагрузках, в основном из-за упаковки и распаковки между типами массива и интерфейса.

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

Вот пример измерения разницы между методами LINQ Count и Any и свойством Array.Length:

Итог: избегайте LINQ, если он вам не нужен.

Одномерные, многомерные или зубчатые

У нас есть три различных типа массивов в C #: одномерные (также известные как векторы), многомерные массивы и зубчатые массивы.

Каждый имеет свой собственный синтаксис и способы, которыми компилятор .NET будет транслировать ваш код. Но в целом различия заключаются в следующем:

  • CLR специально настроен для работы с векторными массивами, и у компилятора .NET есть специальные инструкции для этой цели. В целом они будут более эффективными, чем не-векторные массивы (многомерные).
  • В многомерном массиве всегда будет стандартное количество столбцов в строке.
  • Неровный массив - это массив массивов. По сути, это делает его вектором, который можно использовать вместо многомерного. Если требуется более одного измерения, обычно предпочтительнее использовать массив с зазубринами.

Вот эталон, в котором я сравнил скорость доступа между зубчатыми и многомерными массивами:

Итог: мудро выбирайте типы.

Бонус: достаточно забавного синтаксиса, чтобы всех порадовать

Массивы можно объявлять и инициализировать разными способами, чтобы у нас было достаточно причин для продуктивной (… или нет) войны языков программирования с нашими товарищами по команде и коллегами.

Вот стандартная версия декларации:

Неявный тип local var

Мы можем использовать неявно типизированное ключевое слово var в C #, если тип указан (или выведен, см. Ниже…):

Инициализация во время создания

Массивы можно объявлять и инициализировать одной строкой:

Более короткий путь

Компилятор может автоматически определять тип элементов массива во время инициализации. Поэтому мы можем пропустить объявление типа массива и по-прежнему использовать ключевое слово var.

Еще более короткий путь

Оказывается, нам может не понадобиться новое ключевое слово в конце:

Это заставит ваш компьютер взорваться 🔥 на крошечные кусочки

К сожалению, из-за решений команды .NET следующий код недействителен и вызовет ошибку компиляции:

Ключевые выводы

  • Никогда, никогда больше не выделяйте пустой массив, если у вас нет на то причины. ⛔
  • Ищите использование ArrayPool, когда есть необходимость в больших массивах на горячих путях.
  • Оцените рентабельность использования методов LINQ по сравнению с методами собственного типа. ⚖️
  • Не все массивы одинаковы. ◾️ ◼️ ⬛️
  • Выберите свой любимый синтаксис и проведите пару языковых войн. Живем один раз. ✔️
  • Всегда измеряйте и делайте выводы. ⏰


использованная литература