C ++ std :: map创建耗时太长了?

时间:2011-09-19 18:45:13

标签: c++ stl

更新:

我正在开发一个性能非常关键的程序。我有一个未排序的结构矢量。我需要在这个向量中执行许多搜索操作。所以我决定将矢量数据缓存到这样的地图中:

        std::map<long, int> myMap;

        for (int i = 0; i < myVector.size(); ++i)
        {
            const Type& theType = myVector[i];
            myMap[theType.key] = i;
        }

当我搜索地图时,程序其余部分的结果要快得多。然而,剩下的瓶颈是地图本身的创建(平均花费大约0.8毫秒来插入约1,500个元素)。我需要想办法减少这个时间。我只是插入一个long作为键和一个int作为值。我不明白为什么要这么久。

我的另一个想法是创建一个向量的副本(不能触及原始的一个)并以某种方式执行比std :: sort更快的排序(它需要太长时间才能对它进行排序)。

编辑:

对不起大家。我的意思是说我正在创建一个std :: map,其中键是long,值是int。 long值是struct的键值,int是向量中相应元素的索引。

另外,我做了一些调试,并意识到矢量根本没有排序。这完全是随机的。所以做一些像stable_sort这样的事情就不会有用了。

另一个更新:

感谢大家的回复。我最终创建了一个对的向量(std :: vector of std :: pair(long,int))。然后我按长值对矢量进行排序。我创建了一个自定义比较器,仅查看该对的第一部分。然后我使用lower_bound来搜索该对。以下是我如何做到这一切:

  typedef std::pair<long,int> Key2VectorIndexPairT;
  typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;

  bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
  {
      return pair1.first < pair2.first;
  }

  ...

  Key2VectorIndexPairVectorT sortedVector;
  sortedVector.reserve(originalVector.capacity());

  // Assume "original" vector contains unsorted elements.
  for (int i = 0; i < originalVector.size(); ++i)
  {
      const TheStruct& theStruct = originalVector[i];
      sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
  }

  std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);

  ...

  const long keyToSearchFor = 20;

  const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);

  if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
  {
      const int vectorIndex = cItorKey2VectorIndexPairVector->second;
      const TheStruct& theStruct = originalVector[vectorIndex];

      // Now do whatever you want...
  }
  else
  {
      // Could not find element...
  }

这为我带来了适度的性能提升。在我计算的总时间为3.75毫秒之前,现在它减少到2.5毫秒。

11 个答案:

答案 0 :(得分:6)

std :: map和std :: set都是在二叉树上构建的,因此添加项目会动态分配内存。如果您的地图基本上是静态的(即在开始时初始化一次,然后很少或从未添加或删除新项目),您可能最好使用排序向量和std :: lower_bound来使用二进制搜索查找项目

答案 1 :(得分:3)

地图需要花费大量时间才有两个原因

  • 您需要为数据存储分配大量内存
  • 您需要对排序执行O(n lg n)比较。

如果您只是将其作为一个批处理创建,那么使用自定义池分配器抛出整个地图可能是个好主意 - 例如,boost pool_alloc。自定义分配器还可以应用优化,例如在地图完全销毁之前不会实际释放任何内存等。

由于您的密钥是整数,您可能还需要考虑根据radix tree(在密钥的上)编写自己的容器。这可能会显着提高性能,但由于没有STL实现,您可能需要编写自己的。

如果您不需要对数据进行排序,请使用哈希表,例如std::unordered_map;这些可以避免分类数据所需的大量开销,也可以减少所需的内存分配量。

最后,根据程序的整体设计,简单地重用相同的地图而不是一遍又一遍地重新创建它可能会有所帮助。只需删除并根据需要添加键,而不是构建新的矢量,然后构建新的地图。同样,这可能不适用于您的程序,但如果是,它肯定会对您有所帮助。

答案 2 :(得分:2)

我怀疑内存管理和树重新平衡会让你付出代价。

显然,分析可能会帮助您找出问题所在。

我建议只需将你需要的long / int数据复制到另一个向量中,因为你说它几乎已经排序了,所以在它上面使用stable_sort来完成排序。然后使用lower_bound在已排序的向量中找到项目。

答案 3 :(得分:1)

std :: find是线性扫描(必须是因为它适用于未排序的数据)。如果您可以对数据进行排序(std :: sort guaranties n log(n)行为),那么您可以使用std :: binary_search来获取log(n)搜索。但正如其他人指出的那样,复制时间可能就是问题所在。

答案 4 :(得分:1)

如果键是实心且短的,可以尝试使用std::hash_map。来自MSDN的hash_map Class页面:

  

散列优于分类的主要优点是效率更高;一个   成功散列执行插入,删除和查找   恒定的平均时间与与时间成正比的时间相比   用于排序的容器中元素数量的对数   技术。

答案 5 :(得分:1)

如果您正在创建大型地图并且正在将大量数据复制到地图中,则地图创建可能是性能瓶颈(在某种意义上它需要花费大量时间)。您还使用了将元素插入到std :: map中的明显(但次优)方法 - 如果您使用类似的东西:

myMap.insert(std::make_pair(theType.key, theType));

这应该会提高插入速度,但是如果遇到重复的密钥会导致行为略有变化 - 使用insert将导致重复键的值被删除,而使用您的方法,最后一个元素将重复的密钥插入到地图中。

如果你的分析结果确定它是复制昂贵的元素,我还会考虑避免复制数据(例如通过存储指向它的指针)。但为此你必须对代码进行分析,IME猜测往往是错误的......

另外,作为旁注,您可能希望使用自定义比较器将数据存储在std :: set中,因为您已经包含了密钥。然而,这并不会真正导致大幅加速,因为在这种情况下构建一个集合可能与将其插入地图一样昂贵。

答案 6 :(得分:0)

我不是C ++专家,但似乎您的问题源于复制Type实例,而不是指向Type实例的引用/指针。

std::map<Type> myMap; // <-- this is wrong, since std::map requires two template parameters, not one

如果你向地图添加元素并且它们不是指针,那么我相信复制构造函数被调用,这肯定会导致大数据结构的延迟。改为使用指针:

std::map<KeyType, ObjectType*> myMap;

此外,您的示例有点令人困惑,因为当您期望类型int的值时,您在地图中“插入”了Type类型的值。我认为你应该分配对项目的引用,而不是索引。

myMap[theType.key] = &myVector[i];

更新

我越看你的例子,我就越困惑。如果您正在使用std :: map,那么它应该采用两种模板类型:

map<T1,T2> aMap;

那你真正的映射是什么? map<Type, int>或其他什么?

您似乎正在使用Type.key成员字段作为地图的关键字(这是一个有效的想法),但除非密钥与Type的类型相同,否则您可以'用它作为地图的关键。 keyType ??

的一个实例

此外,您将当前矢量索引映射到地图中的键,这表示您只需要向量的索引,以便以后可以快速访问该索引位置。那是你想要做的吗?

更新2.0:

在阅读完答案之后,您似乎正在使用std::map<long,int>,在这种情况下,不会复制所涉及的结构。此外,您不需要对向量中的对象进行本地引用。如果您只需要访问密钥,请通过调用myVector[i].key来访问该密钥。

答案 7 :(得分:0)

由于矢量已经部分排序,您可能需要创建一个辅助数组,引用原始矢量中元素的索引(索引)。然后,您可以使用Timsort对辅助数组进行排序,这对于部分排序的数据(例如您的数据)具有良好的性能。

答案 8 :(得分:0)

您从您提供的破碎示例中构建该表的副本,而不仅仅是参考。

Why Can't I store references in an STL map in C++?

无论你在地图中存储什么,它都依赖于你不改变矢量。 只尝试查找地图。

typedef vector<Type> Stuff;
Stuff myVector;
    typedef std::map<long, *Type> LookupMap;
    LookupMap myMap;
    LookupMap::iterator hint = myMap.begin();

    for (Stuff::iterator it = myVector.begin(); myVector.end() != it; ++it)
    {
        hint = myMap.insert(hint, std::make_pair(it->key, &*it));
    }

或者可能放下矢量并将其存储在地图中?

答案 9 :(得分:0)

我认为你还有其他一些问题。创建1500 <long, int>对的向量,并根据长整数对它进行排序应该花费相当少于0.8毫秒(至少假设我们正在谈论一个相当现代的桌面/服务器类型处理器)。

为了试着了解我们应该在这里看到的内容,我做了一些测试代码:

#include <vector>
#include <algorithm>
#include <time.h>
#include <iostream>

int main() {

    const int size = 1500;
    const int reps = 100;

    std::vector<std::pair<long, int> > init;
    std::vector<std::pair<long, int> > data;
    long total = 0;

    // Generate "original" array
    for (int i=0; i<size; i++)
        init.push_back(std::make_pair(rand(), i));

    clock_t start = clock();
    for (int i=0; i<reps; i++) {
        // copy the original array
        std::vector<std::pair<long, int> > data(init.begin(), init.end());
        // sort the copy
        std::sort(data.begin(), data.end());

        // use data that depends on sort to prevent it being optimized away
        total += data[10].first;
        total += data[size-10].first;
    }
    clock_t stop = clock();

    std::cout << "Ignore: " << total << "\n";

    clock_t ticks = stop - start;
    double seconds = ticks / (double)CLOCKS_PER_SEC;
    double ms = seconds * 1000.0;
    double ms_p_iter = ms / reps;

    std::cout << ms_p_iter << " ms/iteration.";
    return 0;
}

在我有点“尾随”(〜5岁)的机器上运行它,我得到的时间约为0.1毫秒/迭代。我希望在此搜索(使用std::lower_boundstd::upper_bound)比在std::map中搜索要快一些(因为向量中的数据是连续分配的,我们可以预期更好的引用位置,导致更好的缓存使用率。)

答案 10 :(得分:0)

感谢大家的回复。我最终创建了一个对的向量(std :: vector of std :: pair(long,int))。然后我按长值对矢量进行排序。我创建了一个自定义比较器,仅查看该对的第一部分。然后我使用lower_bound来搜索该对。以下是我如何做到这一切:

      typedef std::pair<long,int> Key2VectorIndexPairT;
      typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;

      bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
      {
          return pair1.first < pair2.first;
      }

      ...

      Key2VectorIndexPairVectorT sortedVector;
      sortedVector.reserve(originalVector.capacity());

      // Assume "original" vector contains unsorted elements.
      for (int i = 0; i < originalVector.size(); ++i)
      {
          const TheStruct& theStruct = originalVector[i];
          sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
      }

      std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);

      ...

      const long keyToSearchFor = 20;

      const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);

      if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
      {
          const int vectorIndex = cItorKey2VectorIndexPairVector->second;
          const TheStruct& theStruct = originalVector[vectorIndex];

          // Now do whatever you want...
      }
      else
      {
          // Could not find element...
      }

这为我带来了适度的性能提升。在我计算的总时间为3.75毫秒之前,现在它减少到2.5毫秒。