← Назад к вопросам

Как List расположен в памяти?

1.0 Junior🔥 171 комментариев
#Управление памятью

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Расположение List<T> в памяти в Unity (C#)

List<T> в C# — это универсальная коллекция, которая внутри использует массив (T[]). Его расположение в памяти можно разделить на несколько ключевых аспектов, критичных для понимания производительности в Unity.

1. Структура данных в памяти

Экземпляр List<T> состоит из двух основных частей, хранящихся в управляемой куче (Managed Heap):

  • Заголовок объекта (Object Header): Служебная информация CLR (Common Language Runtime) о типе объекта, состоянии блокировки и т.д.
  • Поля экземпляра List<T>: Наиболее важные для нас:
    *   `_items` — ссылка на внутренний массив элементов типа `T`.
    *   `_size` — целое число, указывающее текущее количество элементов в списке.
    *   `_version` — служебное поле для контроля изменений во время перечисления.

Сам массив _items также является отдельным объектом в управляемой куче.

// Пример: List<int> myList = new List<int>(4);
// В памяти это выглядит примерно так:

// Объект List<int> в куче:
// [Заголовок] -> [Поле _items:(ссылка)] -> [Поле _size:0] -> [Поле _version:0]

// Отдельный объект - массив int[] в куче (емкость Capacity = 4):
// [Заголовок массива] -> [Длина:4] -> [0] -> [0] -> [0] -> [0]

2. Размещение элементов массива

Элементы внутри массива _items располагаются непрерывно (contiguously) в памяти. Это обеспечивает высокую скорость итераций и доступ по индексу за время O(1), так как процессор эффективно кэширует последовательные участки памяти (принцип локальности данных).

Однако, это справедливо для типов-значений (value types), таких как int, float, Vector3, struct. Для ссылочных типов (reference types), например, GameObject, string, class, в массиве хранятся не сами объекты, а ссылки (указатели) на них. Сами объекты ссылочных типов располагаются в других участках управляемой кучи.

// Для типов-значений (непрерывный блок памяти)
List<Vector3> points = new List<Vector3>();
// В массиве _items лежат непосредственно структуры Vector3.

// Для ссылочных типов (массив ссылок)
List<Enemy> enemies = new List<Enemy>();
// В массиве _items лежат ссылки. Объекты Enemy разбросаны по куче.

3. Механизм увеличения емкости (Capacity) и фрагментация

Изначальная емкость (Capacity) задается в конструкторе или по умолчанию равна 0. Когда количество элементов (Count) достигает Capacity, происходит ресурсоемкая операция выделения нового, большего массива:

  1. Выделяется новый массив (обычно в 2 раза больше предыдущего).
  2. Все существующие элементы копируются из старого массива в новый методом Array.Copy().
  3. Старый массив становится мусором (кандидатом на сборку).

Это приводит к:

  • Пиковому потреблению памяти: На короткий момент в памяти сосуществуют старый и новый массивы.
  • Фрагментации управляемой кучи: Частая переаллокация оставляет "дыры" из освобожденных массивов, что усложняет выделение больших непрерывных блоков памяти в будущем и может спровоцировать сборку мусора (Garbage Collection, GC).
// Плохая практика: провоцирует множественные переаллокации.
List<int> badList = new List<int>();
for (int i = 0; i < 1000; i++) {
    badList.Add(i); // Многократное увеличение Capacity: 0->4->8->16->...
}

// Хорошая практика: минимизация переаллокаций.
List<int> goodList = new List<int>(1000); // Задаем предполагаемую емкость.
for (int i = 0; i < 1000; i++) {
    goodList.Add(i); // Переаллокация не происходит.
}

4. Важность для Unity-разработчика

В контексте оптимизации производительности в Unity понимание устройства List<T> критично:

  • Сборка мусора (GC): Частые операции Add() без предварительного задания Capacity, а также Clear() (который обнуляет ссылки, но НЕ уменьшает массив) создают мусор в виде старых массивов. Для часто изменяемых списков используйте пулы объектов (Object Pooling) или методы Clear() с последующим повторным использованием того же списка.
  • Кэш-промахи (Cache Misses): При работе со списками структур (например, List<MeshData>), если структура (struct) содержит ссылочные типы, данные могут быть разбросаны по памяти, что снижает эффективность кэша процессора. Предпочитайте плоские структуры данных.
  • Неизменяемые данные: Для данных, которые не меняются после инициализации (например, конфигурации уровней), после заполнения списка можно вызвать метод TrimExcess(), чтобы уменьшить внутренний массив до размера, точно равного Count, экономя память.

Итог: List<T> в памяти — это объект-обертка, содержащий ссылку на внутренний массив. Непрерывное хранение элементов массива обеспечивает скорость, но механизм роста емкости является источником потенциальных проблем с производиностью из-за аллокаций и сборки мусора. Грамотное управление емкостью (Capacity) — один из ключевых навыков оптимизации в Unity.

Как List расположен в памяти? | PrepBro