Размер по умолчанию std::vector/Справочник по программированию?
в немецкой книге программирования (начиная с 2012 года) под названием "С++ für C-Programmierer" (С++ для программистов C, duh!), которую я купил в качестве ссылки, я нашел следующий раздел в главе о STL (I ' ll перевести сразу для вас, ребята):
Большинство реализаций STL являются щедрыми с точки зрения управления памятью. Выделение векторов в основном выполняется в кусках 1 кбайт для экземпляров. Вырезки не имеют большого значения, если выделить несколько векторов, но это происходит, если вы создаете от десяти до сотен тысяч.
Я не мог найти источник, подтверждающий это. Я знаю, что это зависит от реализации, но я не мог найти ничего, что подтверждает это даже для одной платформы. Cplusplus.com
просто заявляет:
[...]
Поэтому, по сравнению с массивами, векторы потребляют больше памяти в обмен на способность управлять хранилищем и динамически развиваться эффективным образом.
Что я пробовал до сих пор?
Я написал небольшую программу на С++, использующую специфическую для OS X функцию malloc_size(), но я никогда не использовал ее, и я уверен, что делаю это неправильно. Если я что-то делаю по строкам:
std::vector<int>* i = new std::vector<int>;
std::cout << malloc_size(i) << std::endl;
Cout просто говорит мне 32
, что вполне может быть размером int и, следовательно, доказать, что автор частично ошибается, но я не очень убежден своими силами.
Кто-нибудь знает лучше или знает ресурс?
Спасибо заранее.
С уважением,
Carson
Ответы
Ответ 1
Книга неверна несколькими способами. std::vector
растет в соответствии с геометрическим рядом, поэтому определенный процент всегда будет заполняться (если вы не erase
). Вырезки могут складываться, но в целом это будет доля, пропорциональная используемой памяти. 50-65% - типичная нижняя граница наихудшего случая для фракции, которая фактически используется.
Это не зависит от реализации. Геометрический ряд необходим для обеспечения того, чтобы push_back
брал O (1) амортизированное время. Линейный рост привел бы к O (N) амортизированному времени (или O (N ^ 2) до push_back
последовательности значений N).
Реализация может решить минимальный минимальный размер для непустого vector
, но нет веских оснований для этого, поскольку малые общие. Аналогично, инициализированные по умолчанию векторы неизменно реализуются, чтобы вообще не резервировать динамическую память.
Вам не нужно malloc_size
, чтобы узнать, сколько памяти зарезервировано. Просто используйте v.capacity()
* sizeof( elem_type )
.
Ответ 2
Ваш код не измеряет, что вы хотите его измерить. Сама структура vector
обычно довольно мала. Он содержит в основном несколько полей, необходимых для отслеживания выделенной памяти и указателя на эту память. То, что вы хотите измерить, отличается.
------ -------------------
| i |------> | A few fields |
------ | (e.g., size and |
| capacity) | -------------------
|-----------------| | Space allocated |
| pointer |-------> | for elements |
------------------- -------------------
^ ^
What your code What you want to
measures measure
Возможно, вы можете предоставить настраиваемый распределитель для vector
, который отслеживает и сообщает размер запрошенных распределений. Реализация GCC 4.8.1 на моем компьютере не выделяет память для сконструированного по умолчанию вектора (поскольку он не имеет элементов) и использует реализацию с двойным размером на каждый рост, отмеченную в комментариях.
Ответ 3
Сам векторный объект состоит всего из нескольких указателей, поэтому 32-байтовый размер, который вы показали, не удивительно, и со временем он не изменится.
Я считаю, что текст книги относится к хранилищу, выделенному для содержимого вектора. Когда вы добавляете элементы в вектор, он будет выделять пространство для их хранения, но это пространство не будет отображаться в файле malloc_size.
Вы можете определить, сколько пространства выделил вектор, вызвав векторный метод capacity()
. Это покажет вам, сколько предметов он может удерживать. Если вы хотите размер в байтах, вы можете увеличить емкость по размеру типа элемента.
В цитируемом тексте говорится о блоках размером 1 КБ. Старые динамические контейнеры использовали линейные схемы, когда им нужно было расти. Но требования к сложности выполнения, которые стандартные места на std::vector не позволяют этого подхода. Вместо этого вектор должен расти на некоторый процент от его текущего размера.
Многие реализации используют 100%. Поэтому, если у вектора в настоящее время есть место для 10 предметов, и он должен расти, он будет изменять размер до 20 элементов. Если он должен расти еще дальше, он изменит размер до 40 элементов и так далее. Таким образом, в худшем случае вы можете получить вектор, который выделил почти в два раза больше места, чем это было бы действительно необходимо. В некоторых реализациях используется 50%, что по-прежнему соответствует требованиям сложности во время выполнения, не увеличиваясь настолько быстро или "тратя" столько пространства. (Существует по крайней мере еще одно преимущество использования коэффициента менее 100%, но оно не имеет отношения к этому обсуждению.)
На современном компьютере с виртуальной памятью любой метод обычно хорош - производительность будет более важной, чем неиспользуемая память. Если вы используете встроенную систему с ограниченными ресурсами, вы можете использовать более прямой контроль. Есть трюки, такие как copy-and-swap, которые могут обрезать вектор с избыточной мощностью до размера, близкого к фактической потребности.