Размер по умолчанию 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, которые могут обрезать вектор с избыточной мощностью до размера, близкого к фактической потребности.