Где определена максимальная емкость коллекции С# <T>?

Я попытался добавить большое количество элементов в коллекцию, элементы каждого простого объекта передачи данных с пятью свойствами основных типов данных, ничего особенного.

При добавлении новых записей в цикле я всегда получаю исключение OutOfMemoryException. Интересно, что я всегда получаю исключение при попытке добавить элемент 8388608th (который составляет 8 * 1024 * 1024). Поэтому я предполагаю, что существует встроенный предел в отношении емкости (количества элементов), разрешенных в таких коллекциях, но я не мог найти никакой информации об этом.

Действительно ли этот предел существует? Где я могу найти это документально?

Ответ 1

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

То, что я думаю, происходит, это то, что вы заполняете кучу больших объектов. По мере роста коллекции они должны добавить хранилище в фоновом режиме для размещения новых элементов. Как только новое хранилище будет выделено и элементы будут скопированы, старое хранилище будет выпущено и должно иметь право на сбор мусора.

Проблема заключается в том, что, когда вы выходите за определенный размер (обычно это 85000 байт, но теперь может быть другим), сборщик мусора (GC) отслеживает вашу память, используя что-то, называемое кучей больших объектов (LOH). Когда GC освобождает память от LOH (что редко бывает для начала), память вернется в вашу операционную систему и будет доступна для других процессов, но виртуальное адресное пространство из этой памяти будет по-прежнему использоваться в вашем собственном процессе, У вас будет большая дырка в вашей таблице адресов программ, и поскольку это отверстие находится на большой куче объекта, оно никогда не будет уплотнено или регенерировано.

Причина, по которой вы видите это исключение в точности из двух, состоит в том, что в большинстве коллекций .Net используется алгоритм удвоения для добавления хранилища в коллекцию. Он всегда будет бросаться туда, где ему нужно снова удвоиться, потому что до этого момента RAM уже была выделена.

Таким образом, быстрое решение состоит в том, чтобы использовать малоиспользуемую функцию большинства коллекций .Net. Если вы посмотрите на перегрузки конструктора, большинство типов коллекций будет иметь тот, который позволяет вам установить емкость при первоначальной конструкции. Эта емкость не является жестким пределом; это просто отправная точка; но это полезно в нескольких случаях, в том числе, когда у вас есть коллекции, которые будут расти очень большими. Вы можете установить начальную емкость на что-то непристойное... надеюсь, что-то достаточно простое, чтобы держать все ваши предметы или, по крайней мере, нужно только "удвоить" один или два раза.

Вы можете увидеть этот эффект, запустив следующий код в консольном приложении:

var x = new List<int>();
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

В моей системе это исключает исключение OutOfMemory после элементов 134217728. 134217728 * 4 байта на int только (и точно) 512 МБ ОЗУ. Это не должно бросаться, потому что это единственное вещественное значение любого реального размера в процессе, но оно все равно из-за адресного пространства, потерянного для старых версий коллекции.

Теперь измените код, чтобы установить емкость следующим образом:

var x = new List<int>(134217728 * 2);
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

Теперь моя система добирается до 268435456 элементов (1 ГБ ОЗУ), когда она бросает, что она делает, потому что она не может удваивать 1 ГБ благодаря другому табулятору, используемому процессом, использующим часть таблицы адресов vivutal адреса 2 ГБ limit (т.е. счетчик циклов и любые служебные данные из объекта коллекции и самого процесса).

Я не могу объяснить, что это не позволяет мне использовать 3 как множитель, хотя это будет только (!) 1,5 ГБ. Небольшой эксперимент с использованием разных множителей, пытающихся выяснить, насколько большой я мог получить, показал, что число несовместимо. В какой-то момент я смог подняться выше 2.6, но потом пришлось отступить до уровня ниже 2.4. Что-то новое, чтобы обнаружить, я думаю.

Если для этого решения достаточно места для вас, существует также трюк, который вы можете использовать для получения 3 ГБ виртуального адресного пространства, или вы можете заставить ваше приложение компилировать для x64, а не x86 или AnyCPU. Если вы используете версию фреймворка на основе среды выполнения 2.0 (что-то до .NET 3.5), вы можете попробовать обновить до .Net 4.0 или более поздней версии, что, как сообщается, немного лучше об этом. В противном случае вам придется взглянуть на полную переписку о том, как вы обрабатываете свои данные, которые, вероятно, связаны с хранением на диске, и удерживая только один элемент или небольшой образец элементов (кеш) в памяти за раз. Я действительно рекомендую этот последний вариант, потому что что-то еще, вероятно, неожиданно неожиданно сломается неожиданно (и если вы - набор данных, для которого это очень важно, это, вероятно, также растет).

Ответ 3

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

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

Ответ 4

Предел - это емкость, которую вы можете установить для класса коллекции, и, скорее всего, ее int.MaxValue, который равен 2147483647 (в вашем случае определенно да). Но вы получаете исключение OOM, когда у вас заканчивается память, независимо от того, достигли ли вы этого жесткого предела или нет.