为何MSVC中std::map的emplace_hint性能远低于GCC?
测试代码(改编自cppreference std::map::emplace_hint示例)
#include <chrono> #include <iostream> #include <iomanip> #include <functional> #include <map> const int nof_operations = 1000000; int map_emplace() { std::map<int, char> map; for (int i = 0; i < nof_operations; ++i) { map.emplace(i, 'a'); } return map.size(); } int map_emplace_hint() { std::map<int, char> map; auto it = map.begin(); for (int i = 0; i < nof_operations; ++i) { map.emplace_hint(it, i, 'b'); it = map.end(); } return map.size(); } int map_emplace_hint_wrong() { std::map<int, char> map; auto it = map.begin(); for (int i = nof_operations; i > 0; --i) { map.emplace_hint(it, i, 'c'); it = map.end(); } return map.size(); } int map_emplace_hint_corrected() { std::map<int, char> map; auto it = map.begin(); for (int i = nof_operations; i > 0; --i) { map.emplace_hint(it, i, 'd'); it = map.begin(); } return map.size(); } int map_emplace_hint_closest() { std::map<int, char> map; auto it = map.begin(); for (int i = 0; i < nof_operations; ++i) { it = map.emplace_hint(it, i, 'e'); } return map.size(); } void timeit(std::function<int()> map_test, std::string what = "") { auto start = std::chrono::system_clock::now(); int mapsize = map_test(); auto stop = std::chrono::system_clock::now(); std::chrono::duration<double, std::milli> time = stop - start; if (what.size() > 0 && mapsize > 0) { std::cout << std::fixed << std::setprecision(2) << std::setw(5) << time.count() << " ms for " << what << '\n'; } } int main() { timeit(map_emplace); // stack warmup timeit(map_emplace, "plain emplace"); timeit(map_emplace_hint, "emplace with correct hint"); timeit(map_emplace_hint_wrong, "emplace with wrong hint"); timeit(map_emplace_hint_corrected, "corrected emplace"); timeit(map_emplace_hint_closest, "emplace using returned iterator"); }
不同环境测试结果
- WSL/Ubuntu 环境,编译命令:
g++ Main.cpp -Ofast && ./a.out
179.01 ms for plain emplace 51.59 ms for emplace with correct hint 180.08 ms for emplace with wrong hint 45.99 ms for corrected emplace 47.67 ms for emplace using returned iterator
- Visual Studio MSVC 全优化Release模式
193.36 ms for plain emplace 127.33 ms for emplace with correct hint 206.15 ms for emplace with wrong hint 127.07 ms for corrected emplace 192.91 ms for emplace using returned iterator
- MinGW 环境,编译命令:
g++ Main.cpp -Ofast && a
155.82 ms for plain emplace 104.26 ms for emplace with correct hint 156.06 ms for emplace with wrong hint 99.40 ms for corrected emplace 103.03 ms for emplace using returned iterator
性能差异原因分析
- STL实现逻辑不同
MSVC自带的STL库和GCC使用的libstdc对emplace_hint的hint校验逻辑差异很大。libstdc中只要传入的hint符合插入位置要求,就会直接跳过O(logn)的红黑树搜索步骤,仅执行插入和平衡操作,性能提升非常明显。而MSVC STL的emplace_hint即使拿到正确的hint,也会额外执行多次键值比较,部分版本甚至不会完全跳过树搜索流程,hint带来的性能收益被大幅抵消。
测试中MSVC下emplace using returned iterator场景性能和普通emplace几乎一致,就是因为MSVC STL没有针对“传入上一次插入返回的迭代器作为hint”这种高频场景做专门优化,而libstdc++对该场景做了短路处理,性能提升非常突出。 - 编译器优化策略不同
GCC的-Ofast会开启非常激进的内联优化,能够把emplace_hint的多层模板调用完全展开,消除函数调用开销,同时会对循环内的迭代器操作做冗余代码消除。而MSVC的/O2全优化对STL内部函数的内联阈值设置更高,部分嵌套调用不会被内联,带来额外的开销。 - 红黑树实现细节不同
libstdc++的红黑树实现针对顺序插入场景做了专项优化,当检测到元素按升序/降序连续插入且hint正确时,会直接将新节点挂载到树的最右/最左位置,平衡操作的开销也比随机插入低很多。MSVC的红黑树没有这类针对性优化,顺序插入的平衡开销本身就更高。
内容的提问来源于stack exchange,提问作者user14789259
相关产品推荐
相关产品推荐

