为地图分配值的最有效方法

时间:2013-01-08 15:11:51

标签: c++ dictionary std stdmap c++-standard-library

将值分配给地图的哪种方式最有效?或者它们都针对相同的代码进行了优化(在大多数现代编译器上)?

   // 1) Assignment using array index notation
   Foo["Bar"] = 12345;

   // 2) Assignment using member function insert() and STL pair
   Foo.insert(std::pair<string,int>("Bar", 12345));

   // 3) Assignment using member function insert() and "value_type()"
   Foo.insert(map<string,int>::value_type("Bar", 12345));

   // 4) Assignment using member function insert() and "make_pair()"
   Foo.insert(std::make_pair("Bar", 12345));

(我知道我可以进行基准测试并检查编译器输出,但是这个问题现在出现了,我手边的唯一问题就是我的手机......呵呵)

6 个答案:

答案 0 :(得分:24)

首先,[]insert之间存在语义差异:

  • []替换旧值(如果有)
  • insert忽略新值(如果旧值已存在)
因此,将两者进行比较通常是无用的。

关于插入版本:

  • std::map<std::string, int>::value_type std::pair<std::string const, int>所以3和4之间没有(重要)差异
  • std::make_pair("Bar", 12345)std::pair<std::string, int>("Bar", 12345)便宜,因为std::string类型是一个完整的类,其副作用操作和"Bar"只是一个字符串文字(因此只是一个指针复制);但是,因为最后你需要创建std::string ...它将取决于你的编译器的质量

一般来说,我会建议:

  • []进行更新
  • insert(std::make_pair())忽略重复

std::make_pair不仅更短,还尊重干燥指南:不要重复自己。


为了完整性,最快(最简单)将是emplace(启用C ++ 11):

map.emplace("Bar", 12345);

它的行为是insert的行为,但它会就地构建新元素。

答案 1 :(得分:2)

1)可能比其他方法稍慢,因为std::map::operator[]首先默认创建对象(如果它尚不存在),然后返回一个引用,您可以使用operator=来设置期望值,即两次操作。

2-4)应该是等效的,因为对于相同的类型,map::value_typestd::pair的typedef,因此make_pair也是等效的。编译器应该完全相同。

另请注意,如果您需要同时检查是否存在(例如,根据是否存在而执行特殊逻辑),还可以通过使用map::lower_bound来插入它来进一步提高性能提示元素应该在哪里,因此map::insert不必再次搜索整个map

 // get the iterator to where the key *should* be if it existed:
 std::map::iterator hint = mymap.lower_bound(key);

 if (hint == mymap.end() || mymap.key_comp()(key, hint->first)) {
     // key didn't exist in map
     // special logic A here...

     // insert at the correct location
     mymap.insert(hint, make_pair(key, new_value));
 } else { 
     // key exists in map already
     // special logic B here...

     // just update value
     hint->second = new_value;
 }

答案 2 :(得分:1)

你的第一种可能性:Foo["Bar"] = 12345;的语义与其他语义有些不同 - 如果不存在,它会插入一个新对象(如其他对象),但如果没有,则覆盖当前内容存在(如果该密钥已存在,则使用insert的其他人将失败)。

就速度而言,它有可能比其他速度慢。当您插入新对象时,它创建了一个具有指定键和默认构造的value_type的对,然后分配正确的value_type。其他所有构造对都正确的键和值并插入该对象。然而,公平的是,我的经验是差异很小(对于较旧的编译器,它更重要,但是新的编译器非常小)。

接下来的两个是相同的。您只是使用两种不同的方式来命名相同的类型。通过运行时,它们之间没有任何区别。

第四个使用模板函数(make_pair),理论上可以涉及额外级别的函数调用。我会非常惊讶地看到与此存在真正的区别,除了(可能),如果你小心确保编译器完全没有没有优化(特别是内联)。

底线:第一个通常会比其余的慢一点(但并非总是如此)。其他三个几乎总是相等的(如:通常期望任何合理的编译器为所有三个生成相同的代码),即使第四个的理论上的理由更慢。

答案 3 :(得分:0)

第三个是最佳选择(恕我直言),但2,3和4是相等的。

// 3) Assignment using member function insert() and "value_type()"
Foo.insert(map<string,int>::value_type("Bar", 12345));

为什么我认为第三个是最佳选择:您只执行一个操作来插入值:只插入(好吧,那里也有搜索)和你可以知道是否插入了值,检查返回值的second成员以及实现授予不覆盖该值。

使用value_type也有优势:您不需要知道映射类型或密钥类型,因此对模板编程非常有用。

最糟糕的(恕我直言)是第一个:

// 1) Assignment using array index notation
Foo["Bar"] = 12345;

您正在调用std::map::operator[]创建一个对象并返回对它的引用,然后调用映射的对象operator =。您正在为插入执行两项操作:首先是插入,然后是第二次操作。

还有另一个问题:您不知道该值是否已插入或已被覆盖。

答案 4 :(得分:0)

如果该关键位置没有对象,则:

std::map::emplace效率最高。 insert排名第二(但非常接近)。 []效率最低。

[],如果那里没有对象,则琐碎构造一个。然后它会调用operator=

insertstd::pair参数进行复制构造函数调用。

但是,对于地图,map.insert( make_pair( std::move(key), std::move(value) ) )将接近map.emplace( std::move(key), std::move(value) )

如果关键位置有对象,则[]将调用operator=,而insert / emplace将销毁旧对象并创建新对象。在这种情况下,[]可能会更便宜。

最后,这取决于你的operator= vs copy-construct vs trivial-construct vs析构函数成本对你的密钥和值的影响。

std::map的树形结构中查找实际工作的内容将非常接近相同,这并不好笑。

答案 5 :(得分:0)

尽管已经有一些好的答案,但我认为我不妨做一个快速的基准测试。每次使用500万次并使用c ++ 11的计时来衡量它所用的时间。

下面是代码:

#include <string>
#include <map>
#include <chrono>
#include <cstdio>

// 5 million
#define times 5000000

int main()
{
    std::map<std::string, int> foo1, foo2, foo3, foo4, foo5;
    std::chrono::steady_clock::time_point timeStart, timeEnd;
    int x = 0;

    // 1) Assignment using array index notation
    timeStart = std::chrono::steady_clock::now();
    for (x = 0; x <= times; x++)
    {
        foo1[std::to_string(x)] = 12345;
    }
    timeEnd = std::chrono::steady_clock::now();
    printf("1) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());

    // 2) Assignment using member function insert() and STL pair
    timeStart = std::chrono::steady_clock::now();
    for (x = 0; x <= times; x++)
    {
        foo2.insert(std::pair<std::string, int>(std::to_string(x), 12345));
    }
    timeEnd = std::chrono::steady_clock::now();
    printf("2) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());

    // 3) Assignment using member function insert() and "value_type()"
    timeStart = std::chrono::steady_clock::now();
    for (x = 0; x <= times; x++)
    {
        foo3.insert(std::map<std::string, int>::value_type(std::to_string(x), 12345));
    }
    timeEnd = std::chrono::steady_clock::now();
    printf("3) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());

    // 4) Assignment using member function insert() and "make_pair()"
    timeStart = std::chrono::steady_clock::now();
    for (x = 0; x <= times; x++)
    {
        foo4.insert(std::make_pair(std::to_string(x), 12345));
    }
    timeEnd = std::chrono::steady_clock::now();
    printf("4) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());

    // 5) Matthieu M.'s suggestion of C++11's emplace
    timeStart = std::chrono::steady_clock::now();
    for (x = 0; x <= times; x++)
    {
        foo5.emplace(std::to_string(x), 12345);
    }
    timeEnd = std::chrono::steady_clock::now();
    printf("5) took %i milliseconds\n", (unsigned long long)std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd-timeStart).count());

    return 0;
}

500万次迭代的输出是:

1) took 23448 milliseconds
2) took 22854 milliseconds
3) took 22372 milliseconds
4) took 22988 milliseconds
5) took 21356 milliseconds

GCC版本:

g++ (Built by MinGW-builds project) 4.8.0 20121225 (experimental)

我的机器:

Intel i5-3570k overclocked at 4.6 GHz

EDIT1:修正了代码并重新设置基准。

EDIT2:添加了Matthieu M.对C ++ 11's emplace的建议,他说对了,安慰是最快的