C++std::映射创建耗时过长

C++ std::map creation taking too long?

本文关键字:创建 映射 C++std      更新时间:2023-10-16

更新:

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

        std::map<long, int> myMap;
        for (int i = 0; i < myVector.size(); ++i)
        {
            const Type& theType = myVector[i];
            myMap[theType.key] = i;
        }

当我搜索地图时,程序其余部分的结果要快得多。然而,剩下的瓶颈是地图本身的创建(在其中插入大约1500个元素平均需要大约0.8毫秒(。我需要想办法把这次时间缩短。我只是插入一个long作为键,插入一个int作为值。我不明白为什么要花这么长时间。

我的另一个想法是创建矢量的副本(不能接触原始矢量(,并以某种方式执行比std::sort更快的排序(排序时间太长(。

编辑:

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

此外,我做了更多的调试,并意识到向量根本没有排序。这完全是随机的。所以,做一些类似马厩的运动是行不通的。

另一个更新:

感谢大家的回复。我最终创建了一个成对的向量(std::vector of std::pair(long,int((。然后我按照长值对向量进行排序。我创建了一个自定义比较器,只查看这对的第一部分。然后我用下界搜索这对。以下是我的做法:

  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毫秒。

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

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

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

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

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

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

最后,根据程序的整体设计,简单地重用同一个映射可能会有所帮助,而不是一遍又一遍地重新创建它。只需根据需要删除和添加关键点,而不是构建新的矢量,然后构建新的贴图。同样,这在你的程序中可能是不可能的,但如果是的话,它肯定会对你有所帮助。

我怀疑是内存管理和树重新平衡让你在这里付出了代价。

显然,分析可以帮助您确定问题所在。

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

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

如果键又长又短,可以尝试std::hash_map。来自MSDN的hash_map类页面:

散列比排序的主要优点是效率更高;一成功的哈希在中执行插入、删除和查找与与用于排序的容器中元素数的对数技术。

如果您正在创建一个大型映射并将大块数据复制到其中,则映射创建可能会成为性能瓶颈(从某种意义上说,这需要大量的时间(

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

这应该会提高插入速度,但如果遇到重复键,则会导致行为发生轻微变化-使用insert会导致删除重复键的值,而使用您的方法,会将具有重复键的最后一个元素插入到映射中。

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

此外,作为附带说明,您可能需要考虑使用自定义比较器将数据存储在std::集中,因为您已经包含了键。然而,这并不会真正导致速度的大幅提高,因为在这种情况下构建集合可能会像将其插入地图一样昂贵。

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

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

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

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

此外,您的示例有点令人困惑,因为当您期望Type类型的值时,您在映射中"插入"了int类型的值。我认为你应该将引用指定给项目,而不是索引。

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进行访问。

您根据您给出的坏例子构建了一个表的副本,而不仅仅是一个引用。

为什么可以';我是否将引用存储在C++中的STL映射中?

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

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));
    }

或者把矢量放在地图上??

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

我认为您还有其他问题。创建一个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中搜索快一些(因为向量中的数据是连续分配的,我们可以期待更好的引用位置,从而更好地使用缓存(。

感谢大家的回复。我最终创建了一个成对的向量(std::vector of std::pair(long,int((。然后我按照长值对向量进行排序。我创建了一个自定义比较器,只查看这对的第一部分。然后我用下界搜索这对。以下是我的做法:

      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毫秒。