Эффективность ввода карты 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