【问题标题】:Why is unordered_map and map giving the same performance?为什么 unordered_map 和 map 提供相同的性能?
【发布时间】:2016-08-29 07:12:26
【问题描述】:

这是我的代码,我的 unordered_map 和 map 的行为相同并且执行时间相同。我是否遗漏了这些数据结构的某些内容?

更新:我已根据以下答案和 cmets 更改了我的代码。我删除了字符串操作以减少分析的影响。现在我也只测量 find() ,它在我的代码中占用了近 40% 的 CPU。 profile 显示 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

【问题讨论】:

  • 0.06 非常接近大多数系统上标准时钟的分辨率,所以这一切都意味着两者都花费不到一个滴答声;将你的外循环乘以 100 或更多,看看会发生什么。
  • 您的代码实际上并没有对地图进行任何可观察的操作,因此优化器很可能只是将其删除。
  • 完全优化编译,将迭代次数增加十的几倍,看看是否有区别。
  • 您正在测量字符串的创建和转换时间。
  • 您应该预先说明您是否在打开优化的情况下进行编译。如果您正在编译未优化或“调试”版本,则结果毫无意义。

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


【解决方案1】:

不进入你的代码,我会做一些通用的cmets。

  1. 您具体测量的是什么?您的分析包括填充和扫描数据结构。鉴于(可能)填充有序映射需要更长的时间,测量这两种方法都与有序映射的增益(或其他)的想法背道而驰。弄清楚你在测量什么,然后测量它。
  2. 您的代码中还有很多可能与您正在分析的内容有关:有很多对象创建、字符串连接等。这可能是您实际测量的内容。只专注于分析您想要测量的内容(参见第 1 点)。
  3. 10,000 例太小了。在这种规模下,其他考虑因素可能会压倒您正在衡量的内容,尤其是在您衡量所有内容时。

【讨论】:

  • 感谢您的回答,我已根据您的建议改进了我的代码,现在我正在尝试使此代码更快,因为它在我的应用程序中占用了 40% 的 CPU。
【解决方案2】:

我们喜欢获得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 倍。

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

【讨论】:

  • 你是怎么编译这个的?
  • g++ -std=c++11 so.cpp -o so -O3。实际上,原始编译没有 -O3,但有了它(并将迭代次数再次乘以 10),比率仍然存在。
  • 我很惊讶有任何区别,因为应该优化该代码。
  • 显然,优化器可以看到的内容是有限的。
  • 对于 MSVC15 添加#include
【解决方案3】:

树 (std::map) 还是散列映射 (std::unordered_map) 是否更快取决于条目的数量和键的特性(值的可变性、比较和散列函数等)。 )

但是理论上树比哈希映射慢因为在二叉树中插入和搜索是O(log2(N)) strong> 在哈希映射中插入和搜索时的复杂度大致 O(1)复杂度。

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

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

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

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

  4. 您的 find() 调用很有可能被优化掉,因为您没有使用它们的值(我只是碰巧知道至少 -O2 模式下的 GCC 不会这样做,所以我离开了照原样)。

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

这是我的更新版本:

#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%。

【讨论】:

    【解决方案4】:

    使用 unordered_map 不仅查找速度更快。这个稍作修改的测试还比较了填充时间。

    我做了一些修改:

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

    -

    #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
    

    【讨论】:

      猜你喜欢
      • 2011-01-21
      • 1970-01-01
      • 2016-06-09
      • 2013-11-30
      • 2012-01-17
      • 1970-01-01
      • 2014-05-20
      • 2011-06-18
      相关资源
      最近更新 更多