1. 程式人生 > >C++:探索std::map和std::unordered_map中最高效的新增操作

C++:探索std::map和std::unordered_map中最高效的新增操作

std::map和std::unordered_map主要提供如下幾種新增操作:

  1. try_emplace ()   (C++17)
  2. emplace ()
  3. 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

 從數值上看,這幾種方式的效率相差並不大,可能是編譯器優化的結果。