C++:探索std::map和std::unordered_map中最高效的新增操作
std::map和std::unordered_map主要提供如下幾種新增操作:
- try_emplace () (C++17)
- emplace ()
- insert()
下面給出一段測試程式碼,觀察物件在新增到std::map中時,構造物件過程中會有什麼區別:
#include <map> #include <iostream> #include <tuple> #include <exception> #include <string> #include <memory> #include <vector> using namespace std; class Kid { private: static int cnt; int id; public: string name; int age; Kid() { id = ++cnt; cout << "Kid()" << id << endl; } Kid(string _name, int _age) : name(_name), age(_age) { id = ++cnt; cout << "Kid(string _name, int _age)" << id << endl; } Kid(const Kid&) { id = ++cnt; cout << "Kid(const Kid&)" << id << endl; } Kid(Kid&&) { id = ++cnt; cout << "Kid(Kid&&)" << id << endl; } Kid& operator=(const Kid&) { cout << "operator=(const Kid&)" << id << endl; return *this; } bool operator< (const Kid& rhs) const { return this->age < rhs.age; } }; int Kid::cnt = 0; class People { private: static int cnt; int id; public: string name; int age; People() { id = ++cnt; cout << "People()" << id << endl; } People(string _name, int _age) : name(_name), age(_age) { id = ++cnt; cout << "People(string _name, int _age)" << id << endl; } People(const People&) { id = ++cnt; cout << "People(const People&)" << id << endl; } People(People&&) { id = ++cnt; cout << "People(People&&)" << id << endl; } People& operator=(const People&) { cout << "operator=(const People&)" << id << endl; return *this; } }; int People::cnt = 0; int main() { map<Kid, People> m; cout << "--------------------------------" << endl; { cout << "--------------------------------//(1)" << endl; Kid x1("xxl", 1213332); m.try_emplace(x1, "xlx", 121); } { cout << "--------------------------------//(2)" << endl; m.try_emplace(Kid("xxl", 1213332), "xlx", 121); } { cout << "--------------------------------//(3)" << endl; Kid x1("xxl", 1213332); People p1("xlx", 121); m.emplace(x1, p1); } { cout << "--------------------------------//(4)" << endl; m.emplace(Kid("xxl", 1213332), People("aaaa", 121)); } { cout << "--------------------------------//(5)" << endl; m.emplace(make_pair(Kid("xxl", 1213332), People("aaaa", 121))); } { cout << "--------------------------------//(6)" << endl; m.emplace(std::piecewise_construct, std::forward_as_tuple("fffff", 999), std::forward_as_tuple("wwww", 121)); } { cout << "--------------------------------//(7)" << endl; m.insert(make_pair(Kid("xxl", 1213332), People("aaaa", 121)));; } cout << "--------------------------------" << endl; return 1; }
VC++2017的執行結果:
-------------------------------- --------------------------------//(1) Kid(string _name, int _age)3 Kid(const Kid&)4 People(string _name, int _age)3 --------------------------------//(2) Kid(string _name, int _age)5 Kid(Kid&&)6 People(string _name, int _age)4 --------------------------------//(3) Kid(string _name, int _age)7 People(string _name, int _age)5 Kid(const Kid&)8 People(const People&)6 --------------------------------//(4) People(string _name, int _age)7 Kid(string _name, int _age)9 Kid(Kid&&)10 People(People&&)8 --------------------------------//(5) People(string _name, int _age)9 Kid(string _name, int _age)11 Kid(Kid&&)12 People(People&&)10 Kid(Kid&&)13 People(People&&)11 --------------------------------//(6) Kid(string _name, int _age)14 People(string _name, int _age)12 --------------------------------//(7) People(string _name, int _age)13 Kid(string _name, int _age)15 Kid(Kid&&)16 People(People&&)14 Kid(Kid&&)17 People(People&&)15 --------------------------------
從以上執行結果中可以看到,只講物件構造效率,效率從高到低依次為:
(6) > (2) > (4) > (5) (7) > (1) > (3)
其中(6)只調用了2次建構函式,(2)呼叫了2次建構函式和1次轉移建構函式,(4)呼叫了2次建構函式和2次轉移建構函式,(5)(7)呼叫了2次建構函式和4次轉移建構函式,(1)呼叫了2次建構函式和1次拷貝建構函式,(1)呼叫了2次建構函式和2次拷貝建構函式。但目前還不能說新增操作的效率排名也是一樣。
#include <iostream> #include <sstream> #include <fstream> #include <tuple> #include <exception> #include <string> #include <vector> #include <functional> #include <array> #include <chrono> #include <unordered_map> #include <map> #include <cstdlib> #include <ctime> #include <climits> using namespace std; class Kid_ { private: static int cnt; int id; public: string name; int money; Kid_() { id = ++cnt; } Kid_(string& _name, int& _money) : name(_name), money(_money) { id = ++cnt; } Kid_(const Kid_& k) : name(k.name), money(k.money) { id = ++cnt; } Kid_(Kid_&& k) : name(move(k.name)), money(move(k.money)) { id = ++cnt; } bool operator< (const Kid_& rhs) const { return this->name < rhs.name; } }; int Kid_::cnt = 0; class People_ { private: static int cnt; int id; public: string name; int money; People_() { id = ++cnt; } People_(string& _name, int& _money) : name(_name), money(_money) { id = ++cnt; } People_(const People_& p) : name(p.name), money(p.money) { id = ++cnt; } People_(People_&& p) : name(move(p.name)), money(move(p.money)) { id = ++cnt; } }; int People_::cnt = 0; struct KeyHash { std::size_t operator()(const Kid_& k) const { return std::hash<string>()(k.name) ^ k.money; } }; struct KeyEqual { bool operator()(const Kid_& lhs, const Kid_& rhs) const { return lhs.name == rhs.name && lhs.money == rhs.money; } }; string getRandomString(int length) { string ret; for (int i = 0; i < length; i++) { int r = std::rand() % 10; if (i == 0 && r ==0) { i--; continue; } ret += to_string(r); } return ret; } void testMapAdd() { std::srand(std::time(0)); int times = 1000000; cout << "--------------------------------//map" << endl; { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(1)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); Kid_ k(s1, r1); m.try_emplace(k, s2, r2); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(2)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.try_emplace(Kid_(s1, r1), s2, r2); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(3)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); Kid_ x1(s1, r1); People_ p1(s2, r2); m.emplace(x1, p1); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); if (x1.money >= std::numeric_limits<int>::max()) { cout << endl; } if (p1.money >= std::numeric_limits<int>::max()) { cout << endl; } } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(4)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(Kid_(s1, r1), People_(s2, r2)); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(5)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(make_pair(Kid_(s1, r1), People_(s2, r2))); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; map<Kid_, People_ > m; cout << "--------------------------------//(6)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(std::piecewise_construct, std::forward_as_tuple(s1, r1), std::forward_as_tuple(s2, r2)); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// cout << "--------------------------------//unordered_map" << endl; { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(1)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); Kid_ k(s1, r1); m.try_emplace(k, s2, r2); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(2)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.try_emplace(Kid_(s1, r1), s2, r2); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(3)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); Kid_ x1(s1, r1); People_ p1(s2, r2); m.emplace(x1, p1); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); if (x1.money >= std::numeric_limits<int>::max()) { cout << endl; } if (p1.money >= std::numeric_limits<int>::max()) { cout << endl; } } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(4)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(Kid_(s1, r1), People_(s2, r2)); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(5)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(make_pair(Kid_(s1, r1), People_(s2, r2))); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } { long long timeUsed = 0; unordered_map<Kid_, People_, KeyHash, KeyEqual> m(3 * times); cout << "--------------------------------//(6)" << endl; for (int i = 0; i < times; i++) { string s1 = getRandomString(10); int r1 = std::rand(); string s2 = getRandomString(10); int r2 = std::rand(); auto start = std::chrono::system_clock::now(); m.emplace(std::piecewise_construct, std::forward_as_tuple(s1, r1), std::forward_as_tuple(s2, r2)); auto end = std::chrono::system_clock::now(); timeUsed += std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); } cout << timeUsed << ":\t" << ((float)timeUsed / times) << endl; } } int main() { testMapAdd(); return 0; }
執行結果:
--------------------------------//map --------------------------------//(1) 936421200: 936.421 --------------------------------//(2) 925403300: 925.403 --------------------------------//(3) 985265100: 985.265 --------------------------------//(4) 981455200: 981.455 --------------------------------//(5) 961330000: 961.33 --------------------------------//(6) 991594200: 991.594 --------------------------------//unordered_map --------------------------------//(1) 289984000: 289.984 --------------------------------//(2) 286092800: 286.093 --------------------------------//(3) 283640400: 283.64 --------------------------------//(4) 286383700: 286.384 --------------------------------//(5) 286144700: 286.145 --------------------------------//(6) 282564400: 282.564
從數值上看,這幾種方式的效率相差並不大,可能是編譯器優化的結果。