6.2. Эффективное использование vector
6.2. Эффективное использование vector
Проблема
Вы используете vector, и при этом имеются жесткие требования по объему или времени выполнения кода и требуется снизить или устранить все накладные расходы.
Решение
Поймите, как реализован vector, узнайте о сложности методов вставки и удаления и минимизируйте ненужные операции с памятью с помощью метода reserve. Пример 6.2 показывает некоторые из этих методик в действии.
Пример 6.2. Эффективное использование vector
#include <iostream>
#include <vector>
#include <string>
using std::vector;
using std::string;
void f(vector<string>& vec) {
// Передача vec по ссылке (или,
// если требуется, через указатель)
// ...
}
int main() {
vector<string> vec(500); // При создании vector говорим, что в него
// планируется поместить определенное количество
// объектов
vector<string> vec2;
// Заполняем vec...
f(vec);
vec2 reserve(500); // Или постфактум говорим vector,
// что требуется буфер достаточно большого
// размера для хранения объектов
// Заполняем vec2...
}
Обсуждение
Ключ к эффективному использованию vector лежит в знании его работы. Когда у вас есть четкое представление реализации vector, вопросы производительности становятся очевидными.
Как работает vector
vector — это по сути управляемый массив. Более конкретно, vector<T> — это непрерывный фрагмент памяти (т.е. массив), который достаточно велик для хранения n объектов типа T, где n больше или равно нулю и меньше или равно зависящему от реализации максимальному размеру. Обычно n увеличивается в процессе жизни контейнера при добавлении или удалении элементов, но оно никогда не уменьшается. Что отличает vector от массива — это автоматическое управление памятью массива, методы для вставки и получения элементов и методы, которые предоставляют метаданные о контейнере, такие как размер (число элементов) и емкость (размер буфера), а также информацию о типе: vector<T>::value_type — это тип T, vector<T>::pointer — это тип указатель-на-T и т.д. Два последних и некоторые другие являются частью любого стандартного контейнера, и они позволяют писать обобщенный код, который работает независимо от типа T. Рисунок 6.1 показывает графическое представление того, что предоставляют некоторые из методов vector, если vector имеет размер 7 и емкость 10.
Рис. 6.1. Внутренности vector
Если вам любопытно, как поставщик вашей стандартной библиотеки реализовал vector, скомпилируйте пример 6.1 и пройдите в отладчике все вызовы методов vector или откройте заголовочный файл <vector> реализации стандартной библиотеки и изучите его. Код, который вы там увидите, по большей части не является дружественным к читателю, но он должен осветить некоторые моменты. Во-первых, если вы еще не видели кода библиотеки, он даст вам представление о методиках реализации, используемых для написания эффективного, переносимого обобщенного кода. Во-вторых, он даст точное представление о том, что представляют собой используемые вами контейнеры. При написании кода, который должен работать с различными реализациями стандартной библиотеки, это следует сделать в любом случае.
Однако независимо от поставщика библиотеки почти все реализации векторов похожи. В них есть переменная экземпляра, которая указывает на массив из T, и элементы, добавляемые или присваиваемые вами, с помощью конструктора копирования или операции присвоения помешаются в элементы этого массива.
Обычно добавление объекта T в следующий доступный слот буфера выполняется с помощью копирующего конструктора и new, которому передается тип создаваемого объекта, а также адрес, по которому он должен быть создан. Если вместо этого явно присвоить значение слоту, используя его индекс (с помощью operator[] или at), то будет использован оператор присвоения T. Заметьте, что в обоих случаях объект клонируется либо с помощью конструктора копирования, либо T::operator=. vector не просто хранит адрес добавляемого объекта. Именно по этой причине любой тип, сохраняемый в векторе, должен поддерживать копирующий конструктор и присвоение. Эти свойства означают, что эквивалентный объект типа T может быть создан с помощью вызова конструктора копирования T или оператора присвоения. Это очень важно из-за семантики копирования vector — если конструктор копирования или присвоение объектов не работает, то результаты, получаемые из vector, могут отличаться от того, что в него помещалось. А это плохо.
После добавления некоторого набора объектов в vector его буфер заполняется, и для добавления новых объектов его требуется увеличить. Алгоритм увеличения размера зависит от реализации, но обычно буфер размера n увеличивается до 2n+1. Важным здесь является то, как vector увеличивает свой буфер. Вы не можете просто сказать операционной системе неопределенно увеличить свой фрагмент памяти кучи. Требуется запросить новый фрагмент, который больше уже имеющегося. В результате процесс увеличения размера буфера выглядит следующим образом.
1. Выделить память для нового буфера.
2. Скопировать старые данные в новый буфер.
3. Удалить старый буфер.
Это позволяет vector хранить все его объекты в одном непрерывном фрагменте памяти.
Оптимизация производительности vector
Предыдущий раздел должен дать вам представление о том, как объекты хранятся в векторе. Из этого обзора вам должны стать понятны главные моменты, связанные с производительностью, но в том случае, если вы еще не поняли, я расскажу о них.
Для начала, vector (или любой другой контейнер из стандартной библиотеки) не хранит объекты. Он хранит копии объектов. Это значит, что каждый раз, когда в vector заносится новый объект, он туда не «кладется». С помощью конструктора копирования или оператора присвоения он копируется в другое место. Аналогично при получении значения из vector происходит копирование того, что находится в векторе по указанному индексу, в локальную переменную. Рассмотрим простое присвоение элемента vector локальной переменной.
vector<MyObj> myVec;
// Поместить несколько объектов MyObj в myVec
MyObj obj = myVec[10]; // Скопировать объект с индексом 10
Это присвоение вызывает оператор присвоения obj, в качестве правого операнда которого используется объект, возвращенный myVec[10]. Накладные расходы на производительность при работе с большим количеством объектов резко возрастают, так что их лучше всего избегать.
Для снижения накладных расходов на копирование вместо помещения в vector самих объектов поместите в него указатели. Сохранение указателей потребует меньшего количества циклов ЦП на добавление и получение данных, так как указатели проще скопировать, чем объекты, и, кроме того, это снизит объем памяти, необходимый для буфера vector. Но помните, что при добавлении в контейнер стандартной библиотеки указателей контейнер не удаляет их при своем уничтожении. Контейнеры удаляют только содержащиеся в них объекты, т.е. переменные, которые хранят адреса объектов, но контейнер ничего не знает, хранится ли в нем указатель или объект. Все, что он знает, — это то, что это объект типа T.
Изменение размера буфера тоже не дешево. Копирование каждого элемента буфера требует много работы, и этого лучше всего избегать. Чтобы защититься от этого, явно укажите размер буфера. Имеется пара способов сделать это. Простейшим способом сделать это является указание размера при создании вектора.
vector<string> vec(1000);
Здесь резервируется место для 1000 строк, и при этом производится инициализация каждого слота буфера с помощью конструктора string по умолчанию. При этом подходе приходится платить за создание каждой из этих строк, но добавляются определенные меры безопасности в виде инициализации каждого элемента буфера пустой строкой. Это означает, что при ссылке на элемент, значение которого еще не было присвоено, будет просто получена пустая строка.
Если требуется проинициализировать буфер каким-то определенным значением, можно передать объект, который требуется скопировать в каждый слот буфера.
string defString = "uninitialized";
vector<string> vec(100, defString);
string s = vec[50]; // s = "uninitialized"
В этом варианте vec с помощью конструктора копирования создаст 100 элементов, содержащих значение из defString.
Другим способом резервирования пространства буфера является вызов метода reserve, расположенный после создания vector.
vector<string> vec;
vec reserve(1000);
Главным различием между вызовом reserve и указанием размера в конструкторе является то, что reserve не инициализирует слоты буфера каким-либо значением. В частности, это означает, что не следует ссылаться на индексы, в которые еще ничего не записано.
vector<string> vec(100);
string s = vec[50]; // без проблем: s содержит пустую строку
vector<string> vec2;
vec2.reserve(100);
s = vec2[50]; // Не определено
Использование резервирования или указание числа объектов по умолчанию в конструкторе помогает избежать ненужных перераспределений буфера, Это приводит к увеличению производительности, но также позволяет избежать и еще одной проблемы: каждый раз, когда происходит перераспределение буфера, все итераторы, имевшиеся на этот момент и указывающие на элементы, становятся недействительными.
Наконец, плохой идеей является вставка элементов в любое место, кроме конца вектора. Посмотрите на рис. 6.1. Так как vector — это просто массив с дополнительными прибамбасами, становится очевидно, почему следует добавлять элементы только в конец вектора. Объекты в vector хранятся последовательно, так что при вставке элемента в любое место, кроме конца, скажем, по индексу n, объекты с n+1 до конца должны быть сдвинуты на один (в сторону конца) и освободить место для нового элемента. Сложность этой операции линейна, что означает, что она оказывается дорогостоящей даже для векторов скромного размера. Удаление элемента вектора имеет такой же эффект: оно означает, что все индексы больше n должны быть сдвинуты на один слот вверх. Если требуется возможность вставки и удаления в произвольном месте контейнера, вместо вектора следует использовать list.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
8. Эффективное использование программного обеспечения
8. Эффективное использование программного обеспечения Вы можете посчитать название этой главы несколько странным, однако оно в наилучшей степени отражает те приемы, которые в наши дни используются для управления компьютерными системами. Я надеюсь, что, прежде чем
9. Эффективное использование аппаратных средств
9. Эффективное использование аппаратных средств В современных персональных компьютерах и компьютерных системах программные и аппаратные средства используются весьма тесно, что позволяет создавать такую рабочую среду, о которой несколько лет назад пользователи могли
10. Эффективное использование мультимедийных средств и компьютерных игр
10. Эффективное использование мультимедийных средств и компьютерных игр Поскольку сеть Internet в основном ориентирована на мультимедийные средства, можно прогнозировать резкое увеличение числа подобных средств в ближайшем будущем. Даже корпорация Microsoft создала
Как составить эффективное объявление для контекстной рекламы
Как составить эффективное объявление для контекстной рекламы Мы разобрали одну из важнейших частей рекламного объявления – заголовок. Далее мы рассмотрим несколько рекомендаций, которых необходимо придерживаться при составлении объявления:1. Будьте конкретны.Будет
Эффективное взаимодействие процессов архитектуры Classic Server
Эффективное взаимодействие процессов архитектуры Classic Server В архитектуре Classic Server несколько серверных процессов совместно работают с одной базой данных, осуществляя координацию своих действий через разделяемую таблицу блокировок. Взаимодействие процессов на версиях
76. По умолчанию используйте vector . В противном случае выбирайте контейнер, соответствующий задаче
76. По умолчанию используйте vector. В противном случае выбирайте контейнер, соответствующий задаче РезюмеОчень важно использовать "правильный контейнер". Если у вас есть весомые причины выбрать определенный тип контейнера, используйте тот контейнер, который наиболее
77. Вместо массивов используйте vector и string
77. Вместо массивов используйте vector и string РезюмеИзбегайте реализации абстракция массива посредством массивов в стиле С, арифметики указателей и примитивов управления памятью. Использование vector или string не только сделает проще вашу жизнь, но и позволит написать более
78. Используйте vector (и string::c_str ) для обмена данными с API на других языках
78. Используйте vector (и string::c_str) для обмена данными с API на других языках Резюмеvector и string::c_str служат шлюзом для сообщения с API на других языках. Однако не полагайтесь на то, что итераторы являются указателями; для получения адреса элемента, на который ссылается vector<T>::iterator
Глава 4 Эффективное использование Интернета
Глава 4 Эффективное использование Интернета Поиск требуемой информации в Интернете иногда может занять довольно много времени. Чтобы отыскать нужную ссылку, обычно приходится просматривать большое количество страниц из списка результатов поисковой системы,
Контейнеры vector и string
Контейнеры vector и string Все контейнеры STL по-своему полезны, однако большинство программистов С++ работает с vector и string чаще, чем с их собратьями, и это вполне понятно. Ведь контейнеры vector и string разрабатывались как замена массивов, а массивы настолько полезны и удобны, что
Совет 18. Избегайте vector<bool>
Совет 18. Избегайте vector<bool> Vector<bool> как контейнер STL обладает лишь двумя недостатками. Во-первых, это вообще не контейнер STL. Во-вторых, он не содержит bool.Объект не становится контейнером STL только потому, что кто-то назвал его таковым — он становится контейнером STL лишь
Приложение Отзывы читателей книги «Эффективное делопроизводство на ПК»[1]
Приложение Отзывы читателей книги «Эффективное делопроизводство на ПК»[1] Ирина Кожемяченко, менеджер по продажам, г. Москва:– В наше время без презентации при продажах не обойтись. Неважно, продаете вы продукты питания или компьютерную технику: чтобы убедить покупателя
Вектор (Vector)
Вектор (Vector) vector - вид последовательности, которая поддерживает итераторы произвольного доступа. Кроме того, он поддерживает операции вставки и удаления в конце с постоянным (амортизированным) временем; вставка и удаление в середине занимают линейное время. Управление
3.10. Класс vector
3.10. Класс vector Использование класса vector (см. раздел 2.8) является альтернативой применению встроенных массивов. Этот класс предоставляет гораздо больше возможностей, поэтому его использование предпочтительней. Однако встречаются ситуации, когда не обойтись без