【问题标题】:STL map insertion efficiency: [] vs. insertSTL map插入效率:[] vs. insert
【发布时间】:2011-08-07 05:22:42
【问题描述】:

地图插入有两种方式:

m[key] = val;

或者

m.insert(make_pair(key, val));

我的问题是,哪种操作更快? 人们通常说第一个比较慢,因为 STL 标准首先在 map 中不存在“key”时“插入”一个默认元素,然后将“val”分配给默认元素。

但我不认为第二种方式更好,因为'make_pair'。与pair<T1, T2>(key, val) 相比,make_pair 实际上是一种方便的“配对”方式。无论如何,他们都做了两个分配,一个是将'key'分配给'pair.first',两个分配'val'给'pair.second'。 pair 生成后,map 会插入 'pair.second' 初始化的元素。

所以第一种方式是 1. 'default construct of typeof(val)' 2. 赋值 第二种方式是 1. assignment 2. 'copy construct of typeof(val)'

【问题讨论】:

标签: c++ stl map insert std-pair


【解决方案1】:

两者完成不同的事情。

m[key] = val;

如果key 不存在,将插入一个新的键值对,或者如果它已经存在,它将覆盖映射到key 的旧值。

m.insert(make_pair(key, val));

仅当key 尚不存在时才会插入该对,它永远不会覆盖旧值。因此,请根据您想要完成的任务进行选择。
对于更有效的问题:profile。 :P 可能是我想说的第一种方式。分配(又名副本)是两种方式的情况,因此唯一的区别在于构造。众所周知并且应该实现,默认构造基本上应该是无操作的,因此非常有效。副本就是这样 - 副本。因此,在方式一中,我们得到“无操作”和副本,在方式二中,我们得到两个副本。
编辑:最后,请相信您的分析告诉您的内容。我的分析与@Matthieu 在他的评论中提到的一样,但这是我的猜测。 :)


然后,我们有 C++0x 来了,第二条路上的双重复制将是无效的,因为现在可以简单地移动这对。所以最后,我想还是回到我的第一点:用正确的方式完成你想做的事情。

【讨论】:

  • +1 用于从语义而不是性能中进行选择。我认为分析有点偏离。第一种方法应该更慢(默认构造),因为在这两种情况下都会复制键和值。
  • @Matthieu:嗯,确实如此。我将进行一些编辑,但会让我的分析保持不变。这再次表明,分析真的会告诉你什么是更快的。
  • std::map<>::operator[] 不会默认初始化值,它会初始化值 值,因此像std::map<K, std::array<int, 1000>>::operator[] 这样的东西可能会非常浪费。
【解决方案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。详情见讨论here

    但是,如果您在插入运算符上使用“提示”功能,则在特殊情况下可能会获得性能提升。因此,从here 中查看选项2

    iterator insert (const_iterator position, const value_type& val);
    

    如果您给出良好的提示,“插入”操作可以减少到恒定时间(从 log(n) 开始)(如果您知道要在地图后面添加东西,通常是这种情况)。

    【讨论】:

      【解决方案4】:

      我们必须通过提到相对性能还取决于被复制对象的类型(大小)来改进分析。

      我用 (int -> set) 的地图做了一个类似的实验 (to nbt)。我知道这是一件很糟糕的事情,但是,这说明了这种情况。 “值”,在本例中是一组整数,其中包含 20 个元素。

      我执行了 []= Vs 的一百万次迭代。插入操作并执行 RDTSC/iter-count。

      [] = 设置 | 10731个周期

      插入(make_pair)| 26100次循环

      它显示了由于复制而增加的惩罚幅度。当然,CPP11(move ctor's) 将改变图片。

      【讨论】:

        【解决方案5】:

        我的看法: 值得提醒的是,maps 是一棵平衡二叉树,大部分修改和检查都需要 O(logN)。

        实际上取决于您要解决的问题。

        1) 如果您只想插入该值,但知道该值尚不存在, 那么 [] 会做两件事: a) 检查物品是否存在 b)如果不存在,将创建对并执行插入操作( O(logN)) 的双重工作,所以我会使用插入。

        2) 如果您不确定它是否存在,那么 a) 如果您确实通过执行 if(map.find(item) == mp.end()) 之类的操作来检查该项目是否存在上面某处的行,然后使用插入,因为双重工作 [] 会执行 b)如果你没有检查,那么它取决于,因为插入不会修改值,如果它在那里,[] 会,否则它们是相等

        【讨论】:

          【解决方案6】:

          我的答案不是效率而是安全,这与选择插入算法有关:

          []insert() 调用将触发元素的析构函数。如果你的析构函数内部有关键行为,这可能会产生危险的副作用。

          在这样的危险之后,我不再依赖 STL 的隐式延迟插入功能,并且总是使用显式检查我的对象是否在其 ctors/dtors 中具有行为。

          看到这个问题: Destructor called on object when adding it to std::list

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-02-04
            • 1970-01-01
            • 1970-01-01
            • 2011-02-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多