【问题标题】:std::vector is faster than std::map for a key lookup?std::vector 比 std::map 更快的键查找?
【发布时间】:2018-04-21 21:43:04
【问题描述】:

我一直在使用 std::vector,想知道是否应该使用 std::map 进行键查找以提高性能。

这是我的完整测试代码。

#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <ctime>
#include <chrono>

using namespace std;

vector<string> myStrings = {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo", "ppp", "qqq", "rrr", "sss", "ttt", "uuu", "vvv", "www", "xxx", "yyy", "zzz"};

struct MyData {

    string key;
    int value;
};

int findStringPosFromVec(const vector<MyData> &myVec, const string &str) {

    auto it = std::find_if(begin(myVec), end(myVec),
                           [&str](const MyData& data){return data.key == str;});
    if (it == end(myVec))
        return -1;
    return static_cast<int>(it - begin(myVec));
}

int main(int argc, const char * argv[]) {

    const int testInstance = 10000; //HOW MANY TIMES TO PERFORM THE TEST

    //----------------------------std::map-------------------------------
    clock_t map_cputime = std::clock(); //START MEASURING THE CPU TIME

    for (int i=0; i<testInstance; ++i) {

        map<string, int> myMap;

        //insert unique keys
        for (int i=0; i<myStrings.size(); ++i) {

            myMap[myStrings[i]] = i;
        }
        //iterate again, if key exists, replace value;
        for (int i=0; i<myStrings.size(); ++i) {

            if (myMap.find(myStrings[i]) != myMap.end())
                myMap[myStrings[i]] = i * 100;
        }
    }
    //FINISH MEASURING THE CPU TIME
    double map_cpu = (std::clock() - map_cputime) / (double)CLOCKS_PER_SEC;
    cout << "Map Finished in " << map_cpu << " seconds [CPU Clock] " << endl;


    //----------------------------std::vector-------------------------------
    clock_t vec_cputime = std::clock(); //START MEASURING THE CPU TIME

    for (int i=0; i<testInstance; ++i) {

        vector<MyData> myVec;

        //insert unique keys
        for (int i=0; i<myStrings.size(); ++i) {

            const int pos = findStringPosFromVec(myVec, myStrings[i]);

            if (pos == -1)
                myVec.push_back({myStrings[i], i});
        }
        //iterate again, if key exists, replace value;
        for (int i=0; i<myStrings.size(); ++i) {

            const int pos = findStringPosFromVec(myVec, myStrings[i]);

            if (pos != -1)
                myVec[pos].value = i * 100;
        }
    }
    //FINISH MEASURING THE CPU TIME
    double vec_cpu = (std::clock() - vec_cputime) / (double)CLOCKS_PER_SEC;
    cout << "Vector Finished in " << vec_cpu << " seconds [CPU Clock] " << endl;
    return 0;
}

这就是我得到的结果。

Map Finished in 0.38121 seconds [CPU Clock] 
Vector Finished in 0.346863 seconds [CPU Clock] 
Program ended with exit code: 0

我通常在一个容器中存储少于 30 个元素。

这是否意味着在我的情况下使用 std::vector 而不是 std::map 更好?

编辑:当我在循环前移动 map&lt;string, int&gt; myMap; 时,std::map 比 std::vector 快。

Map Finished in 0.278136 seconds [CPU Clock] 
Vector Finished in 0.328548 seconds [CPU Clock] 
Program ended with exit code: 0

所以如果这是正确的测试,我猜 std::map 更快。

但是,如果我将元素数量减少到 10 个,std::vector 会更快,所以我猜这真的取决于元素的数量。

【问题讨论】:

  • 您的地图计时包括多次填充地图。向量没有。要获得真实的比较,请在计时代码之外填充地图。
  • 使用基准标记库,您无需考虑缓存加热等问题。
  • 您将线性时间查找与对数时间查找进行比较。伙计来吧使用正确的数据结构,您不会将苹果与花生进行比较
  • 您可能已经知道这一点,但 unordered_mapunordered_set 在任何时候(对于大型数据集)查找时都比它们中的任何一个要快得多。

标签: c++ dictionary vector


【解决方案1】:

我想说的是,一般来说,向量的性能可能比地图更好,但仅适用于少量数据,例如你提到的元素少于 30 个。

原因是通过连续内存块的线性搜索是访问内存的最便宜的方式。地图将数据保存在随机内存位置,因此访问它们的成本会更高一些。如果元素数量很少,这可能会起作用。在具有成百上千个元素的现实生活中,查找操作的算法复杂性将主导这种性能提升。

但是!您正在对完全不同的事物进行基准测试:

  1. 您正在填充地图。如果是矢量,则不要这样做
  2. 您的代码可以执行两次地图查找:首先,find 来检查是否存在,第二个 [] 运算符来查找要修改的元素。这些都是相对繁重的操作。你可以只用一个 find 修改一个元素(自己想办法,检查参考!)
  3. 在每次测试迭代中,您都在执行额外的繁重操作,例如为每个映射/向量分配内存。这意味着您的测试不仅要衡量查找性能,还要衡量其他方面。
  4. 基准测试是一个难题,不要自己做。例如,存在缓存加热等副作用,您必须处理它们。使用类似Celerohayaigoogle benchmark

【讨论】:

  • 只谈前两段:是的,在这种情况下,内存是连续的原因是向量持有std::string 的值,这些值很可能适合库的“小字符串优化” " range - 因此整个字符串都在连续的内存中。给定答案here 加上一个典型的64 byte cache line width,这个字符串向量适合 10 个缓存行 - 顺序访问因此预取。地图到处都是。这个向量搜索比较冷比热好!
  • 让我澄清一下:它也比地图更好地工作,只是一旦地图 - 也很小 - 都被放入缓存中,优势就会减少。
  • @davidbak 不错的补充!为了简单起见,我特意没有提及缓存和相关主题。
【解决方案2】:

您的向量具有不变的内容,因此编译器无论如何都会优化您的大部分代码。
测量如此小的计数几乎没有用处,测量硬编码值也没有用处。

【讨论】:

  • “所以编译器无论如何都会优化你的大部分代码” -- 值得怀疑。
猜你喜欢
  • 2012-02-05
  • 2011-05-08
  • 2016-05-24
  • 1970-01-01
  • 2014-10-24
  • 2016-09-02
  • 1970-01-01
  • 2014-04-12
  • 1970-01-01
相关资源
最近更新 更多