【问题标题】:C++ std::map creation taking too long?C++ std::map 创建时间太长?
【发布时间】:2011-11-20 12:07:00
【问题描述】:

更新:

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

        std::map<long, int> myMap;

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

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

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

编辑:

对不起大家。我的意思是说我正在创建一个 std::map ,其中键是长整数,值是整数。 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 毫秒。

【问题讨论】:

  • 你没说Type是什么。也许它有一个昂贵的复制构造函数?
  • 抱歉,“Type”是一个包含大约 10 个 double 值和一个 long 值(键)的结构。我什至没有将类型复制到地图中。向量中只有键和对应的索引。
  • 或更有可能有 1500 多次新的调用。
  • faster sort than the std::find。您的意思是比std::sort 更快吗?
  • 代码不正确。 std::map 需要两个模板参数(键和值)。

标签: c++ stl


【解决方案1】:

我不是 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&lt;Type, int&gt; 还是别的什么?

您似乎使用Type.key 成员字段作为映射的键(这是一个有效的想法),但除非键与Type 的类型相同,否则您不能将其用作地图的钥匙。那么keyType 的实例吗??

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

更新 2.0:

阅读您的答案后,您似乎正在使用std::map&lt;long,int&gt;,在这种情况下,没有复制所涉及的结构。此外,您不需要对向量中的对象进行本地引用。如果您只需要访问密钥,则通过调用myVector[i].key 访问它。

【讨论】:

  • 对于容器的映射,还需要在模板中指定比较对象。
  • @Mooing Duck,我认为他的示例完全搞砸了,因为他使用Type.key 字段作为映射的键,然后他分配了一个整数来代替该键。也许他的意思是将键映射到保存对象的向量中的索引,在这种情况下,他不需要比较函数。
  • 哇,我打错了。对于键为指针的关联容器,您需要在模板中指定一个比较对象。 std::map&lt;keytype, valuetype, comparisontype&gt; Mark 展示了一个演示比较对象。
  • @Mooning Duck,我同意,但我什至不明白他是如何尝试使用它的,因为 std::map 采用模板参数,而不是一个。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    如果键是实心和短的,也许可以试试std::hash_map。来自 MSDN 的页面hash_map Class

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

    【讨论】:

    • 我尝试了stdext::hash_map数据结构,并没有发现任何性能差异。
    【解决方案4】:

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

    【讨论】:

    • std::binary_search 是标准库中最具误导性的名称;它可以进行二进制搜索,但只返回 bool 说明该项目是否在向量中。你真正想要的是 std::lower_bound
    • 如果您正在创建一个新的用于排序的键和索引向量,即std::vector&lt;std::pair&lt;long,int&gt; &gt;,您需要在该新向量上调用reserve,这样只有一个内存分配。
    • 啊,是的,我忘记了 binary_search 的奇怪命名选择。编辑澄清。
    【解决方案5】:

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

    显然,分析可能可以帮助您查明问题。

    我建议将您需要的 long/int 数据复制到另一个向量中,并且由于您说它几乎已排序,因此我建议在其上使用 stable_sort 来完成排序。然后使用 lower_bound 定位排序向量中的项目。

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

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

          如果您只是将其创建为一个批次,然后将整个地图扔掉,在这里使用自定义池分配器可能是一个好主意 - 例如,boost 的pool_alloc。自定义分配器还可以应用优化,例如在地图完全销毁之前不实际释放任何内存等。

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

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

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

          【讨论】:

            【解决方案9】:

            您根据您提供的损坏示例构建表格的副本,而不仅仅是参考。

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

            或者也许删除矢量并将其存储在地图中??

            【讨论】:

              【解决方案10】:

              我认为你还有其他问题。创建一个包含 1500 个 &lt;long, int&gt; 对的向量,并根据 long 对其进行排序应该花费不到 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 中搜索要快一些(因为向量中的数据是连续分配的,我们可以期待更好的参考位置,领先以更好地使用缓存)。

              【讨论】:

                【解决方案11】:

                感谢大家的回复。我最终创建了一个对向量(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 毫秒。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-01-02
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-18
                  • 1970-01-01
                  相关资源
                  最近更新 更多