简体   繁体   English

unordered_map 与带有 int 键的向量

[英]unordered_map vs vector with int keys

What is the difference in performance between using an std::unordered_map or a std::vector when the keys are integers.当键是整数时,使用std::unordered_mapstd::vector在性能上有何不同。 I have around 100-1000 elements which have continuous IDs that I can use to access a vector.我有大约 100-1000 个具有连续 ID 的元素,我可以使用它们来访问向量。 The reason to use a hash table is that is more convenient to index by the objects themselves.使用 hash 表的原因是通过对象本身进行索引更方便。

Imagine three different situations:想象三种不同的情况:

  • Write intensive写密集型
  • Read intensive精读
  • Balanced均衡

Note that I ask it as a general question, no as a code specific one.请注意,我将其作为一般性问题提出,而不是作为特定于代码的问题。

Use the most convenient container for the task使用最方便的容器来完成任务

Generally speaking.通常来说,一般来说。

If you have something like 100-1000 elements, the container doesn't really matter in itself - using a std::map even is better than std::unordered_map for example if you ever need to debug print the content - unless you somehow rely on the hash.如果你有100-1000元素,容器本身并不重要——使用std::map甚至比std::unordered_map更好,例如,如果你需要调试打印内容——除非你以某种方式依赖在哈希上。 It's when you get to something like 100k+ elements that container performance starts to get interesting.当您获得 10 万多个元素时,容器性能开始变得有趣。

Generally:一般来说:

Vector has better cache coherence due to lack of indirection.由于缺乏间接性,Vector 具有更好的缓存一致性。 Accessing an index is faster because there is no need to calculate a hash function.访问索引更快,因为不需要计算散列函数。 Iteration has predictable branches.迭代具有可预测的分支。

Unordered map uses less memory with sparse structures (you said that indices are continuous, so this advantage does not apply to you).无序映射使用稀疏结构使用更少的内存(你说索引是连续的,所以这个优势不适用于你)。 Adding or removing elements in arbitrary indices is asymptotically faster with unordered map.使用无序映射在任意索引中添加或删除元素的速度越来越快。

Asymptotic complexity doesn't necessarily matter when you have only very few elements such as 100-1000.当您只有很少的元素(例如 100-1000)时,渐近复杂性不一定重要。 Cache coherency and branch prediction tend to dominate in this case.在这种情况下,缓存一致性和分支预测往往占主导地位。

First pick which ever data structure is more convenient.首先选择哪种数据结构更方便。 Then measure if accessing that structure has significant impact on performance of the program as a whole.然后衡量访问该结构是否对整个程序的性能产生重大影响。 If it does, then measure the difference with the other data structure to see if it is significantly faster (in relation to variance of measurement).如果是,则测量与其他数据结构的差异,看看它是否明显更快(与测量方差有关)。

vector vs map have critical differences in how they treat keys. vectormap在处理键的方式上存在重大差异。 Ignore performance, and use whichever one has the right behavior for keys when a node is deleted.忽略性能,并在删除节点时使用对密钥具有正确行为的任何一个。

If you have less than ~1000 items, then performance doesn't matter.如果您的项目少于 ~1000 个,那么性能并不重要。

I just wondered about the same question.我只是想知道同样的问题。 Although I generally agree that one should in most cases use the most convenient container and also that, when in doubt, you should measure yourself, I think it is nice to have at least a general idea of the dimensions we're talking about.虽然我通常同意在大多数情况下应该使用最方便的容器,并且当有疑问时,您应该自己测量,但我认为至少对我们正在谈论的尺寸有一个大概的了解是很好的。

Hence, I implemented a small example:因此,我实现了一个小例子:

#include <string>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <chrono>

struct MyStruct {
    int value;
};


int main(int argc, char** argv) {
    size_t const SIZE = 100;
    size_t const ACCESSES = 10000;

    std::vector<MyStruct const*> my_structs;
    for (size_t i = 0; i < SIZE; ++i) {
        my_structs.push_back(new MyStruct());
    }
    
    std::vector<double> values_vec(my_structs.size());
    std::unordered_map<MyStruct const*, double> values_map;
    for (size_t i = 0; i < SIZE; ++i) {
        double rand_val = (rand() % 1000)/10;

        values_vec[i] = rand_val;
        values_map[my_structs[i]] = rand_val;
    }

    std::vector<MyStruct const*> queries_ptr;
    std::vector<size_t> queries_int;
    for (size_t i = 0; i < ACCESSES; ++i) {
        size_t idx = rand() % SIZE;
        queries_int.push_back(idx);
        queries_ptr.push_back(my_structs[idx]);
    }

    auto begin_vec = std::chrono::steady_clock::now();
    double large_sum_vec = 0;
    for (size_t query : queries_int) {
        large_sum_vec += values_vec[query];
    }
    auto end_vec = std::chrono::steady_clock::now();

    double large_sum_map = 0;
    for (MyStruct const* query : queries_ptr) {
        large_sum_map += values_map[query];
    }
    auto end_map = std::chrono::steady_clock::now();

    std::cout << "Results for " << ACCESSES << " accesses to vector of size " << SIZE << std::endl;
    std::cout << "=== VEC === Result = " << large_sum_vec << " Time = " << std::chrono::duration_cast<std::chrono::microseconds>(end_vec - begin_vec).count() << " microseconds" << std::endl;
    std::cout << "=== MAP === Result = " << large_sum_vec << " Time = " << std::chrono::duration_cast<std::chrono::microseconds>(end_map - end_vec).count() << " microseconds" << std::endl;

    for (size_t i = 0; i < SIZE; ++i) {
        delete my_structs[i];
    }
}

You can find it here on Coliru: https://coliru.stacked-crooked.com/a/a986dd2607a8566a您可以在 Coliru 上找到它: https://coliru.stacked-crooked.com/a/a986dd2607a8566a

The result: std::vector with indices is about 10-20 times faster than std::unordered_map for random read-access结果:带索引的 std::vector 比随机读取访问的 std::unordered_map 快大约 10-20 倍

Interestingly, both running times are almost independent of the data size.有趣的是,两种运行时间几乎都与数据大小无关。 With increasing numbers of accesses the relation shifts more towards 1:20.随着访问次数的增加,该关系更趋向于 1:20。 I did not test write-intensive / mixed, but I don't expect that there is much of a difference, since the element is already found at this point.我没有测试写密集型/混合型,但我不认为会有太大差异,因为此时已经找到了元素。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM