【问题标题】:stl map performance?stl地图性能?
【发布时间】:2012-04-27 06:51:59
【问题描述】:

我正在使用map<MyStruct, I*> map1;。显然,我的应用程序总时间的 9% 都花在了那里。特别是我的主要职能之一。地图不是很大(

是否有我可能想要使用的替代实现?我认为我不应该自己写,但如果我认为这是个好主意,我可以。

附加信息:我总是在添加元素之前进行检查。如果存在密钥,我需要报告问题。之后,我将大量使用 map 进行查找,并且不会添加更多元素。

【问题讨论】:

  • 没有源代码,我们真的无法分辨。另请查看返回一对的insert 版本(这将回答您的第二个问题)
  • 您能否在MyStruct 上分享地图也使用的比较功能的信息?
  • 您能否提供更多关于您在上述功能中所做的工作的信息?由于 map 的查找复杂度为 O(log n),我不确定应该从哪里改进。
  • 您是否在优化分析?

标签: c++ performance algorithm profiling


【解决方案1】:

如果密钥已经存在,只需执行insert 并检查pair.second 是否为false 可能会更快:

喜欢这个

if ( myMap.insert( make_pair( MyStruct, I* ) ).second == false)
{
  // report error
}
else
  // inserted new value

...而不是每次都打一个find

【讨论】:

  • 好主意。虽然添加成员不符合性能问题。我将添加它并查看性能
  • 如果您将查找推迟到您是否真的有冲突,您可能会消除大量不必要的查找,除非您正在做一些相当奇怪的事情并且生成的重复值多于唯一值。我详细介绍的insert 方法返回一对first 是新值或重复值的迭代器,第二个是布尔值,true 表示成功,false 表示重复条目。
  • 我觉得不这样插入有点傻。我一定很着急,或者认为这是一次性代码。
【解决方案2】:

这可能是一个长镜头,但对于小型收藏,有时最关键的因素是cache 的性能。

由于std::map 实现了一个红黑树,这 [AFAIK] 缓存效率不是很高 - 也许将地图实现为 std::vector<pair<MyStruct,I*>> 是一个好主意,并在那里使用二进制搜索 [而不是地图外观-ups],至少在您开始仅查找 [停止插入元素] 时它应该是有效的,因为 std::vectormap 更可能适合缓存。

这个因素 [cpu-cache] 通常在大 O 表示法中被忽略并隐藏为常数,但对于大型集合,它可能会产生重大影响。

【讨论】:

  • 我可能会有一个在内部使用两个向量而不是一对的类,并使其看起来类似于地图。
  • @acidzombie24:pair 只是一个建议。关于两个向量:我同意,它可能确实比pairs 的vector 更好[更少的字段-> 更少的内存使用-> 整个地图更有可能适合缓存]。答案旨在仅强调缓存对小集合的重要性,并提醒它通常比大 O 符号更重要,因为不应忽略常量。
  • 在某些情况下,当从地图切换到矢量时,我们的代码有了很大的改进。我们怀疑使用向量的缓存性能要好得多。
【解决方案3】:

你可以试试unordered_map,而不是map,它使用哈希键而不是树来查找元素。 This answer 给出了一些提示,什么时候更喜欢 unordered_map 而不是 map

【讨论】:

  • 对于小型集合,散列映射通常比红黑树慢,所以我希望它在这种情况下的使用只会产生负面影响。
  • @amit:即使对于一个包含 20 个元素的小集合,this simple comparison——在简单键类型的限制下——给了我更好的性能 unordered_set。真正的比较将包括MyStruct 和它的哈希函数。
  • 我查看了 unordered_map。在阅读了错误消息(它的大......)后,我意识到我需要实现一个散列函数。我不知道该怎么做。我如何散列一个char *?长度可以是 1 到 20 个字符(或更多)
  • @acidzombie24:标准库提供了模板std::hash,您可以将其用作hash function for std::string。如果您从char* 切换到std::string,也许您不需要将它显式指定为unordered_map 的模板参数。还有this stackoverflow question 要求字符串散列函数。
【解决方案4】:

如 cmets 中所述,没有正确的代码,几乎没有通用的答案可以给你。但是,如果MyStruct 真的很大,则堆栈复制可能会很昂贵。也许存储指向MyStruct 的指针并实现自己的比较机制是有意义的:

template <typename T> struct deref_cmp {
  bool operator()(std::shared_ptr<T> lhs, std::shared_ptr<T> rhs) const {
    return *lhs < *rhs;
  }
};

std::map<std::shared_ptr<MyStruct>, I*, deref_cmp<MyStruct>> mymap;

但是,这是您必须进行概要分析的内容。它可能加快速度。

你会查找这样的元素

template <typename T> struct NullDeleter {
  void operator()(T const*) const {}
};
// needle being a MyStruct
mymap.find(std::shared_ptr<MyStruct>(&needle,NullDeleter()));

不用说,还有更多的优化潜力。

【讨论】:

  • MyStruct 实际上是 T* ,其中 T 是模板 ;)。请参阅我对 Johann Gerell 回答的第一条评论
  • 好吧,在这种情况下,您不必为这种重定向而烦恼。
  • 是的。所以无论如何+1的建议:)
【解决方案5】:

首先,您需要了解地图是什么以及您正在执行的操作代表什么。 std::map 是一棵平衡二叉树,查找将采用 O( log N ) 操作,每个操作都是键的比较加上一些在大多数情况下可以忽略的额外内容(指针管理)。插入需要大致相同的时间来定位插入点,加上新节点的分配、实际插入树和重新平衡。复杂度又是O( log N ),虽然隐藏的常数更高。

当您尝试在插入之前确定某个键是否在映射中时,您将产生查找成本,如果查找不成功,则定位插入点的成本相同。您可以通过使用 std::map::insert 来避免额外的成本,它返回一对带有迭代器和一个布尔值,告诉您插入是否实际发生或元素已经存在。

除此之外,您需要了解比较密钥的成本有多大,这超出了问题所显示的范围(MyStruct 只能容纳一个 int 或一千个),这是您需要的考虑到。

最后,map 可能不是满足您需求的最有效的数据结构,您可能需要考虑使用预期恒定时间插入的std::unordered_map(哈希表)(如果散列函数不是可怕)或对于小型数据集,甚至是一个普通的有序数组(或std::vector),您可以在其上使用二进制搜索来定位元素(这将减少分配的数量,以更昂贵的插入为代价,但如果持有的类型足够小,它可能是值得的)

与以往一样,对性能进行衡量,然后尝试了解时间都花在了哪些地方。另请注意,在特定函数或数据结构上花费 10% 的时间可能很多或几乎没有,这取决于您的应用程序是什么。例如,如果您的应用程序只是在数据集中执行查找和插入操作,并且只占用 10% 的 CPU,那么您在其他任何地方都需要进行大量优化!

【讨论】:

  • 优秀的答案。一个建议是无序的(散列)可能很糟糕,因为表的大小很小。我肯定会在我的其他位置使用插入。你抓住了所有重要的点
  • 您可以使用lower_bound 替代盲插入,检查密钥,然后使用提示插入(如果您想避免潜在的昂贵对象构造)。
  • 该结构实际​​上是我编写的一个快速包装模板,它包含 T*。关键是像&lt;== 这样的操作会做*ptr OP *other 所以它不会通过ptr 地址进行比较。使用容器时,它让我的生活变得非常轻松。那是另一个很好的提示。 +1 @KerrekSB
【解决方案6】:

是否有我可能想要使用的替代实现?我认为我不应该自己写,但如果我认为这是个好主意,我可以。

如果你足够了解问题,你应该详细说明你的实现将如何更好。

map 是正确的结构吗?如果是这样,那么您的标准库的实现可能质量很好(经过优化)。

MyStruct比较可以简化吗?

问题出在哪里——调整大小?查找?

您是否将结构的复制和分配成本降至最低?

【讨论】:

  • 问题:查找。正确的结构:也许。我需要通过键找到一个结构(需要与其他结构进行比较)以及与之关联的接口对象。我不认为订单是一个问题,因为它通常很小。复制/分配:嗯,我通过复制一个ptr来分配。它是不可变的,所以我不必担心它会被删除。
  • 我正在检查我的 cmp 函数中的两个结构是否为空。删除它使它足够快,不再出现。 +1 整体答案。
【解决方案7】:

您使用地图的方式是基于MyStruct 实例进行查找,并且根据您的特定实现,所需的比较可能会或可能不会很昂贵。

【讨论】:

  • 嗯,我认为我不需要按特定顺序使用它。我意识到大部分代码都在检查这两个变量是否为空,我不认为它曾经是。只需删除该检查就足以让它从分析器中消失(我很惊讶)。如果它再次出现,我会更多地使用比较功能。 +1 并可能接受。
猜你喜欢
  • 2017-02-27
  • 2014-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多