为什么unordered_map和map提供相同的性能?

时间:2016-08-29 07:12:26

标签: c++ performance data-structures unordered-map

这是我的代码,我的unordered_map和map的行为相同,并且执行时间相同。我错过了一些关于这些数据结构的内容吗?

更新:我已根据以下答案和评论更改了我的代码。我删除了字符串操作以减少分析中的影响。现在我只测量find(),它占用了我代码中近40%的CPU。该配置文件显示unordered_map的速度提高了3倍,但有没有其他方法可以使这段代码更快?

#include <map>
#include <unordered_map>
#include <stdio.h>

struct Property {
    int a;
};

int main() {
    printf("Performance Summery:\n");
    static const unsigned long num_iter = 999999;

    std::unordered_map<int, Property > myumap;
    for (int i = 0; i < 10000; i++) {
        int ind = rand() % 1000;
        Property p;
        p.a = i;
        myumap.insert(std::pair<int, Property> (ind, p));
    }

    clock_t tStart = clock();
    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        std::unordered_map<int, Property >::iterator itr = myumap.find(ind);
    }

    printf("Time taken unordered_map: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);

    std::map<int, Property > mymap;
    for (int i = 0; i < 10000; i++) {
        int ind = rand() % 1000;
        Property p;
        p.a = i;
        mymap.insert(std::pair<int, Property> (ind, p));
    }

    tStart = clock();
    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        std::map<int, Property >::iterator itr = mymap.find(ind);
    }

    printf("Time taken map: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
}

输出在这里

Performance Summery:
Time taken unordered_map: 0.12s
Time taken map: 0.36s

4 个答案:

答案 0 :(得分:6)

如果不进入您的代码,我会做一些一般性评论。

  1. 你究竟在测量什么?您的分析包括填充和扫描数据结构。鉴于(推测)填充有序地图需要更长的时间,测量两者的作用与有序地图的增益(或其他)的想法相反。弄清楚你在测量什么,然后测量它。
  2. 你在代码中也有很多事情可能是你正在分析的内容:有很多对象创建,字符串连接等等。这可能是你实际测量的内容。专注于仅分析您想要测量的内容(参见第1点)。
  3. 10,000个案件太小了。在这种情况下,其他考虑因素可能会超出您的测量范围,特别是当您测量所有内容时。

答案 1 :(得分:5)

我们有理由得到minimal, complete and verifiable个例子。这是我的代码:

#include <map>
#include <unordered_map>
#include <stdio.h>

struct Property {
    int a;
};

static const unsigned long num_iter = 100000;
int main() {
    printf("Performance Summery:\n");
    clock_t tStart = clock();
    std::unordered_map<int, Property> myumap;

    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        Property p;
        //p.fileName = "hello" + to_string(i) + "world!";
        p.a = i;
        myumap.insert(std::pair<int, Property> (ind, p));
    }

    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        myumap.find(ind);
    }

    printf("Time taken unordered_map: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);

    tStart = clock();
    std::map<int, Property> mymap;

    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        Property p;
        //p.fileName = "hello" + to_string(i) + "world!";
        p.a = i;
        mymap.insert(std::pair<int, Property> (ind, p));
    }

    for (int i = 0; i < num_iter; i++) {
        int ind = rand() % 1000;
        mymap.find(ind);
    }

    printf("Time taken map: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
}

运行时间是:

Performance Summery:
Time taken unordered_map: 0.04s
Time taken map: 0.07s

请注意,我运行的迭代次数是运行的10倍。

我怀疑你的版本有两个问题。首先,你运行的迭代太少,无法发挥作用。第二个是你在计数循环中进行昂贵的字符串操作。运行字符串操作所花费的时间大于使用无序映射所节省的时间,因此您没有看到性能上的差异。

答案 2 :(得分:2)

树(std::map)或哈希映射(std::unordered_map)是否更快取决于条目的数量和密钥的特征(值的可变性,比较和散列)功能等。)

理论上树比哈希映射慢,因为在二叉树内插入和搜索是 O(log2(N))复杂性大致 O(1)复杂性。

您的测试没有显示,因为:

  1. 您在循环中调用rand()。与地图插入相比,这需要很长时间。它会为您正在测试的两个地图生成不同的值,甚至会进一步扭曲结果。使用重量较轻的发电机,例如minstd LCG。

  2. 您需要更高分辨率的时钟和更多迭代,以便每次测试运行至少需要几百毫秒

  3. 您需要确保编译器不重新排序您的代码,以便定时调用发生在应有的位置。这并不总是那么容易。定时测试周围的记忆围栏通常有助于解决这个问题。

  4. 由于你没有使用他们的值,你find()次调用很有可能被优化掉(我碰巧知道至少GCC在-O2模式下没有这样做,所以我保持原样。)

  5. 字符串连接相比也很慢。

  6. 这是我的更新版本:

    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <map>
    #include <random>
    #include <string>
    #include <unordered_map>
    
    using namespace std;
    using namespace std::chrono;
    
    struct Property {
      string fileName;
    };
    
    const int nIter = 1000000;
    
    template<typename MAP_TYPE>
    long testMap() {
      std::minstd_rand rnd(12345);
      std::uniform_int_distribution<int> testDist(0, 1000);
      auto tm1 = high_resolution_clock::now();
      atomic_thread_fence(memory_order_seq_cst);
      MAP_TYPE mymap;
    
      for (int i = 0; i < nIter; i++) {
        int ind = testDist(rnd);
        Property p;
        p.fileName = "hello" + to_string(i) + "world!";
        mymap.insert(pair<int, Property>(ind, p));
      }
      atomic_thread_fence(memory_order_seq_cst);
    
      for (int i = 0; i < nIter; i++) {
        int ind = testDist(rnd);
        mymap.find(ind);
      }
    
      atomic_thread_fence(memory_order_seq_cst);
      auto tm2 = high_resolution_clock::now();
      return (long)duration_cast<milliseconds>(tm2 - tm1).count();
    }
    
    int main()
    {
      printf("Performance Summary:\n");
      printf("Time taken unordered_map: %ldms\n", testMap<unordered_map<int, Property>>());
      printf("Time taken map: %ldms\n", testMap<map<int, Property>>());
    }
    

    Compiled with -O2,它给出了以下结果:

    Performance Summary:
    Time taken unordered_map: 348ms
    Time taken map: 450ms
    

    因此在中使用unordered_map此特定情况的速度提高约20-25%。

答案 3 :(得分:1)

使用unordered_map,查找不仅速度更快。这个稍微修改过的测试也会比较填充时间。

我做了一些修改:

  1. 增加样本量
  2. 这两张地图现在使用相同的随机数序列。
  3. -

    #include <map>
    #include <unordered_map>
    #include <vector>
    #include <stdio.h>
    
    struct Property {
        int a;
    };
    
    struct make_property : std::vector<int>::const_iterator
    {
        using base_class = std::vector<int>::const_iterator;
        using value_type = std::pair<const base_class::value_type, Property>;
        using base_class::base_class;
    
        decltype(auto) get() const {
            return base_class::operator*();
        }
    
        value_type operator*() const
        {
            return std::pair<const int, Property>(get(), Property());
        }
    };
    
    int main() {
        printf("Performance Summary:\n");
        static const unsigned long num_iter = 9999999;
    
        std::vector<int> keys;
        keys.reserve(num_iter);
        std::generate_n(std::back_inserter(keys), num_iter, [](){ return rand() / 10000; });
    
    
        auto time = [](const char* message, auto&& func)
        {
            clock_t tStart = clock();
            func();
            clock_t tEnd = clock();
            printf("%s: %.2gs\n", message, double(tEnd - tStart) / CLOCKS_PER_SEC);
        };
    
        std::unordered_map<int, Property > myumap;
        time("fill unordered map", [&]
        {
            myumap.insert (make_property(keys.cbegin()),
                           make_property(keys.cend()));
        });
    
    
        std::map<int, Property > mymap;
        time("fill ordered map",[&]
             {
                 mymap.insert(make_property(keys.cbegin()),
                              make_property(keys.cend()));
             });
    
        time("find in unordered map",[&]
             {
                 for (auto k : keys) { myumap.find(k); }
             });
    
        time("find in ordered map", [&]
             {
                 for (auto k : keys) { mymap.find(k); }
             });
    }
    

    示例输出:

    Performance Summary:
    fill unordered map: 3.5s
    fill ordered map: 7.1s
    find in unordered map: 1.7s
    find in ordered map: 5s