将值分配给地图的哪种方式最有效?或者它们都针对相同的代码进行了优化(在大多数现代编译器上)?
// 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));
(我知道我可以进行基准测试并检查编译器输出,但是这个问题现在出现了,我手边的唯一问题就是我的手机......呵呵)
答案 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_type
是std::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=
。
insert
对std::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的建议,他说对了,安慰是最快的