我正在开发一个时间关键型应用程序,我正在寻找最佳容器来处理以下类型的元素集合:
class Element
{
int weight;
Data data;
};
考虑到我的应用程序的时间关键步骤,在一个独特的线程中定期执行,如下:
Element
weight
data
,并处理Element
; weight
的数字n> = 0,随机(*)Element
被插入容器中。容器的某些Element
可能具有相同的重量。容器中任何时候元素的总数都很高,平均几乎是静止的(几十万)。上述提取/处理/插入序列所需的时间必须尽可能低。 (注意(*):新的权重实际上是根据数据计算的,但在这里被认为是随机的,以简化。)
在对不同的STL容器进行一些搜索和尝试之后,我最终使用了 std :: multiset 容器,其执行速度比命令 std :: vector 快约5倍,速度提高了16倍订购标准:列表。但是,考虑到我的应用程序的瓶颈仍然存在于提取/插入操作中,我想知道是否可以实现更好的性能。
请注意,虽然我只尝试了有序的容器,但我没有在我的要求中提到“有序容器”。我不需要在容器中订购<Style x:Key="myImageAnimateStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)"
BeginTime="0:0:0" Duration="0:0:0.5"
From="1.0" To="0.0" RepeatBehavior="Forever" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
,我只需要尽快执行“提取最低加权元素”/“插入新元素”操作。我不仅限于STL容器,如果适合的话,可以使用boost或任何其他实现。
谢谢你的帮助。
答案 0 :(得分:3)
我不需要在容器中订购元素,我只需要尽快执行“提取最低加权元素”/“插入新元素”操作。
然后您应该vector<T>
尝试priority_queue<T>
,或对Element
使用make_heap
/push_heap
/pop_heap
操作。
由于您正在寻找最小堆,而不是最大堆,因此您需要提供一个自定义比较器,以相反的顺序对{{1}}个对象进行排序。
答案 1 :(得分:1)
我认为在STL中,懒惰的std::vector
会给出最好的结果。
建议的伪代码可能如下所示:
通过这种方式,您可以获得vector
的摊销插入时间,相对较少的内存分配和良好的缓存局部性。
答案 2 :(得分:1)
考虑不同的候选人以及您的假设将如何影响最终选择是有益的。当您的需求发生变化时,切换容器变得更加容易。
通常,大小为N
的容器的基本访问/修改操作大致有3种复杂性类别:(摊销)O(1)
,O(log N)
和O(N)
。
您的第一个要求(找到最低权重元素)为您提供了大约三个具有O(1)
复杂度的候选人,以及一个每个元素具有O(N)
复杂度的候选人:
O(1)
std::priority_queue<Element, LowestWeightCompare>
O(1)
std::multiset<Element, LowestWeightCompare>
O(1)
boost::flat_multiset<Element, LowestWeightCompare>
O(N)
std::unordered_multiset<Element>
第二个要求(随机插入新元素)为上述四个选项中的每一个提供了以下复杂度每个元素
O(log N)
std::priority_queue
O(log N)
std::multiset
O(N)
boost::flat_multiset
O(1)
std::unordered_multiset
醇>
在前三个选项中,boost::multiset
应该由大N
的其他两个选项支配。在剩余的两个中,std::priority_queue
优于std::multiset
的缓存行为可能占上风。但是:衡量,衡量,衡量。
std::unorderd_multiset
是否与其他三个竞争是先验不明确的。根据随机插入元素的数量n
,find(1)-insert(n)
的每批总费用O(N) search + O(n) insertion
为std::unordered_multiset
,O(1) search + O(n log N) insertion
为std::multiset
。同样,衡量,衡量,衡量。
这些考虑因素对您的要求有多强大?如果您必须在每批中找到k
最低权重元素,则故事会发生如下变化。然后你必须比较find(k)-insert(n)
的费用。搜索成本大致会缩放为
O(k log N)
std::priority_queue
{li> O(1)
std::multiset
{li> O(1)
boost::flat_multiset
{li> O(k N)
std::unordered_multiset
醇>
请注意,priority_queue
只能有效地访问top元素,而不是k
个顶级元素,而不会实际调用pop()
,每次调用的复杂度为O(log N)
。如果您希望代码可能会从find(1)-insert(n)
批处理模式更改为find(k)-insert(n)
,那么选择std::multiset
或者至少记录哪种界面可能是个好主意它需要的改变。
奖励:两全其美?!您可能还想尝试使用Boost.MultiIndex并使用类似的内容(查看文档以获得正确的语法)
boost::multi_index<
Element,
indexed_by<
ordered_non_unique<member<Element, &Element::weight>, std::less<>>,
hashed_non_unique<>
>
>
上面的代码将创建一个基于节点的容器,该容器实现两个指针结构,以跟踪Element
权重的排序,并允许快速哈希插入。这将允许O(1)
查找最低权重Element
,并允许O(n)
随机插入n
个新元素。
对于大N
,它应该比前面提到的四个容器更好地扩展,但同样,对于中等N
,指针追逐到随机存储器中引起的缓存效应可能会破坏其理论上的优势{{1} 1}}。我是否提到了衡量,衡量,衡量的口号?
答案 3 :(得分:0)
尝试其中任何一种:
std::map<int,std::vector<Data>>
或
std::unordered_map<int,std::vector<Data>>
以上int
是重量。
这两者都有不同的查找,删除和添加速度,具体取决于许多不同因素,例如元素是否存在。 (如果有,unordered_map .find更快,如果没有,map .find更快)