По сравнению с C++, сборщик мусора C кажется волшебством, и вы можете очень легко писать код, не беспокоясь о базовой памяти. Но если вы заботитесь о производительности, знание того, как среда выполнения. NET управляет своей оперативной памятью, поможет вам писать более качественный код.
Типы значений и ссылочные типы
В. NET есть два типа типов, которые напрямую влияют на то, как обрабатывается базовая память.
Типы значений - это примитивные типы с фиксированными размерами, такие как
int
,
bool
,float
,
double
и т. д. Они передаются по значению, то есть если вы вызываете
someFunction(int arg)
аргумент копируется и отправляется как новое место в памяти.
Под капотом типы значений (обычно) хранятся в стеке. В основном это относится к локальным переменным, и есть множество исключений, когда они вместо этого хранятся в куче. Но во всех случаях место в памяти, где находится тип значения, содержит фактическое значение этой переменной.
Стек - это просто особое место в памяти, инициализированное значением по умолчанию, но способное расширяться. Стек представляет собой структуру данных «последний пришел - первый ушел» (LIFO). Вы можете думать об этом как о ведре - переменные добавляются в верхнюю часть ведра, и когда они выходят за пределы области действия,. NET лезет в ведро и удаляет их по одной, пока не доберется до дна.
Стек намного быстрее, но это все же просто место в ОЗУ, а не специальное место в кеше ЦП (хотя он меньше, чем куча, и поэтому, скорее всего, будет горячим в кеше, что помогает с производительностью).
Стек получает большую часть своей производительности от своей структуры LIFO. Когда вы вызываете функцию, все переменные, определенные в этой функции, добавляются в стек. Когда эта функция возвращает значение и эти переменные выходят за пределы области видимости, стек очищает все, что поместила в него функция. Среда выполнения управляет этим с помощью кадров стека, которые определяют блоки памяти для различных функций. Выделение стека происходит очень быстро, потому что просто записывается одно значение в конец фрейма стека.
Именно отсюда появился термин StackOverflow, который возникает, когда функция содержит слишком много вызовов вложенных методов и заполняет весь стек.
Ссылочные типы,, однако, либо слишком велики, либо не имеют фиксированных размеров, либо живут слишком долго, чтобы находиться в стеке. Обычно они принимают форму созданных объектов и классов, но также включают в себя массивы и строки, размер которых может различаться.
Ссылочные типы, такие как экземпляры классов, часто инициализируются ключевым словом
new, которое создает новый экземпляр класса и возвращает ссылку на него. Вы можете установить это в локальную переменную, которая фактически использует стек для хранения ссылки на местоположение в куче.
Куча может расширяться и заполняться до тех пор, пока у компьютера не закончится память, что делает ее отличной для хранения большого количества данных. Однако он неорганизован, и в C для его правильной работы необходимо управлять сборщиком мусора. Выделение в куче также происходит медленнее, чем в стеке, хотя и достаточно быстро.
Однако из этих правил есть ряд исключений, иначе значения и ссылочные типы назывались бы «типами стека» и «типами кучи».
-
Внешние переменные лямбда-функций, локальные переменные блоков
IEnumerator
и локальные переменные
асинхронных методов хранятся в куче.
- Поля типов значений классов являются долговременными переменными и всегда хранятся в куче. Они также заключены в ссылочный тип и хранятся вместе с этим ссылочным типом.
- Статические поля класса также всегда хранятся в куче.
- Пользовательские структуры являются типами значений, но они могут содержать ссылочные типы, такие как списки и строки, которые обычно хранятся в куче. Создание копии структуры создает новую копию и выделение всех ссылочных типов в куче.
Наиболее заметным исключением из правила «ссылочные типы, находящиеся в куче» является использование
stackalloc
с
Span, который вручную выделяет блок памяти в стеке для временного массива, который будет очищен из стека как обычно, когда он выйдет за пределы области видимости. Это обходит относительно дорогое выделение кучи и оказывает меньшее давление на сборщик мусора в процессе. Это может быть намного более производительным, но это немного расширенная функция, поэтому, если вы хотите узнать о ней больше, вы можете прочитать это руководство о том, как правильно ее использовать, не вызывая исключения StackOverflow.
Что такое сборка мусора?
Стек очень организован, но куча беспорядочна. Без чего-то, что могло бы управлять этим, вещи в куче не очищаются автоматически, что приводит к тому, что вашему приложению не хватает памяти из-за того, что она никогда не освобождается.
Конечно, это проблема, поэтому существует сборщик мусора. Он работает в фоновом потоке и периодически сканирует ваше приложение на наличие ссылок, которые больше не существуют в стеке, что указывает на то, что программа перестала заботиться о данных, на которые ссылаются. Среда выполнения. NET может выполнять очистку и перемещать память в процессе, чтобы сделать кучу более организованной.
Однако за это волшебство приходится платить - сборка мусора выполняется медленно и дорого. Он работает в фоновом потоке, но есть период, когда выполнение программы должно быть остановлено, чтобы запустить сборку мусора. Это компромисс, который приходит с программированием на C; все, что вы можете сделать, это попытаться свести к минимуму создаваемый вами мусор.
В языках без сборщика мусора вам нужно вручную очищать за собой, что во многих случаях быстрее, но больше раздражает программиста. Так что, в некотором смысле, сборщик мусора похож на Roomba, который убирает ваши полы автоматически, но медленнее, чем просто встать и пропылесосить.