Эффективность ввода карты STL: [] против вставки
Существует два способа вставки карты:
m[key] = val;
или
m.insert(make_pair(key, val));
Мой вопрос: какая операция быстрее?
Обычно люди говорят, что первый из них медленнее, потому что STL Standard сначала "вставляет" элемент по умолчанию, если "ключ" не существует на карте, а затем присваивает значение "val" элементу по умолчанию.
Но я не вижу второго способа лучше из-за "make_pair". make_pair на самом деле является удобным способом сделать "пару" по сравнению с pair<T1, T2>(key, val)
. Во всяком случае, оба они выполняют два назначения, один присваивает "ключ" "пара.первой", а два назначает "val" на "пар.секунд". После создания пары карта вставляет элемент, инициализированный 'pair.second'.
Итак, первый способ - 1. 'default construct of typeof(val)
' 2. назначение
второй способ - 1. назначение 2. 'copy construct of typeof(val)
'
Ответы
Ответ 1
Оба выполняют разные вещи.
m[key] = val;
Введет новую пару ключ-значение, если key
уже не существует или перезапишет старое значение, сопоставленное с key
, если оно уже существует.
m.insert(make_pair(key, val));
Будет только вставить пару, если key
еще не существует, она никогда не перезапишет старое значение. Итак, выберите в соответствии с тем, что вы хотите выполнить.
На вопрос, что более эффективно: профиль.: P Вероятно, первый способ, который я сказал бы. Назначение (aka copy) имеет место для обоих способов, поэтому единственная разница заключается в построении. Как мы все знаем и должны реализовывать, построение по умолчанию должно быть в принципе без-op и, следовательно, быть очень эффективным. Копия именно это - копия. Таким образом, мы получаем "нет-op" и копию, а по очереди мы получаем две копии.
Изменить. В конце концов, доверьтесь тому, что говорит вам профилирование. Мой анализ был отключен, как @Matthieu упоминает в своем комментарии, но это было мое предположение.:)
Затем мы получаем С++ 0x, а двойная копия на втором пути будет ничтожной, так как пару можно просто переместить. Поэтому, в конце концов, я думаю, что он возвращается к моему первому пункту: используйте правильный способ выполнить то, что вы хотите сделать.
Ответ 2
В слегка загруженной системе с большим количеством памяти этот код:
#include <map>
#include <iostream>
#include <ctime>
#include <string>
using namespace std;
typedef map <unsigned int,string> MapType;
const unsigned int NINSERTS = 1000000;
int main() {
MapType m1;
string s = "foobar";
clock_t t = clock();
for ( unsigned int i = 0; i < NINSERTS; i++ ) {
m1[i] = s;
}
cout << clock() - t << endl;
MapType m2;
t = clock();
for ( unsigned int i = 0; i < NINSERTS; i++ ) {
m2.insert( make_pair( i, s ) );
}
cout << clock() - t << endl;
}
дает:
1547
1453
или аналогичные значения при повторных запусках. Поэтому вставка (в этом случае) немного быстрее.
Ответ 3
Производительность мудрая, я думаю, что они в основном одни и те же в целом. Могут быть некоторые исключения для карты с большими объектами, и в этом случае вы должны использовать [] или, возможно, emplace, который создает меньше временных объектов, чем "insert". Подробнее см. Обсуждение здесь.
Однако вы можете получить балл производительности в особых случаях, если вы используете функцию "hint" в операторе вставки. Итак, посмотрев опцию 2 из здесь:
iterator insert (const_iterator position, const value_type& val);
операция "вставка" может быть сведена к постоянному времени (из журнала (n)), если вы дадите хороший намек (часто бывает, если вы знаете, что добавляете вещи в конце своей карты).
Ответ 4
Нам нужно уточнить анализ, указав, что относительная производительность зависит от типа (размера) копируемых объектов.
Я сделал аналогичный эксперимент (до nbt) с картой (int → set). Я знаю, что это ужасно, но это иллюстративно для этого сценария. "Значение", в этом случае набор ints, содержит в нем 20 элементов.
Я выполняю миллион итераций [] = Vs. вставить операции и выполнить RDTSC/iter-count.
[] = set | 10731 циклов
insert (make_pair < > ) | 26100 циклов
Он показывает величину штрафа, добавленную за счет копирования. Конечно, CPP11 (переместить ctor's)
изменит изображение.
Ответ 5
Я принимаю это:
Стоит напомнить, что карты представляют собой сбалансированное двоичное дерево, большинство изменений и проверок принимают O (logN).
Зависит от проблемы, которую вы пытаетесь решить.
1), если вы просто хотите вставить значение, зная, что его еще нет,
то [] будет делать две вещи:
а) проверьте, есть ли предмет или нет
б) если его нет, будет создаваться пара и делать то, что делает вставка ( двойная работа O (logN)), поэтому я бы использовал вставку.
2), если вы не уверены, что он есть или нет, тогда: а) если вы проверили, есть ли элемент, выполнив что-то вроде if (map.find(item) == mp.end()) пара строк выше, а затем использовать вставку, из-за двойной работы [] будет выполняться b) если вы не проверили, то это зависит, потому что вставка не изменит значение, если оно есть, [] будет, в противном случае они будут равно
Ответ 6
Мой ответ не на эффективность, а на безопасность, которая имеет отношение к выбору алгоритма вставки:
Вызовы []
и insert()
вызовут деструкторы элементов. Это может иметь опасные побочные эффекты, если, скажем, ваши деструкторы ведут себя критически.
После такой опасности я перестал полагаться на неявные ленивые функции вставки STL и всегда использовал явные проверки, если у моих объектов есть поведение в их ctors/dtors.
Смотрите этот вопрос:
Деструктор вызывается для объекта при добавлении его в std::list