1

我正在实现快速行进算法,这是某种连续的 Dijkstra。正如我在许多论文中所读到的,斐波那契堆是最适合此目的的堆。

但是,当使用 callgrind 分析我的代码时,我看到以下函数占用了 58% 的执行时间:

int popMinIdx () {
    const int idx = heap_.top()->getIndex();
    heap_.pop();
    return idx; 
}

具体来说,pop()它占用了整个执行时间的 57.67%。

heap_定义如下:

boost::heap::fibonacci_heap<const FMCell *, boost::heap::compare<compare_cells>> heap_;

花费“那么多”时间是否正常,或者我可以做些什么来提高性能?

抱歉,如果没有提供足够的信息。我尽量简短。如果需要,我会添加更多信息。

谢谢!

4

2 回答 2

4

其他答案并没有提到重要的部分:当然 pop() 会占用您大部分时间:它是执行任何实际工作的唯一函数!

您可能已经阅读过,斐波那契堆的操作界限是摊销界限。这意味着如果您以良好的顺序执行足够的操作,则边界将平均为该值。但是,实际成本是完全隐藏的。

每次插入元素时,什么都不会发生。它只是被扔到根列表中。繁荣,O(1)时间。每次合并两棵树时,它的根都会链接到根列表中。繁荣,O(1)时间。但是等等,你的结构不是一个有效的斐波那契堆!这就是 pop()(或 extract-root)的用武之地:每次调用此操作时,整个 Heap 都会重新调整为正确的形状。根被删除,它的孩子被切到根列表,然后我们开始合并根列表中的树,这样根列表中就不会存在两棵具有相同度数(孩子数)的树。

所以 Insert(e) 和 Merge(t) 的所有工作实际上都被延迟到 Pop() 被调用,然后它会完成所有工作。其他操作呢?

删除(e)很漂亮。我们执行 Decrease-Key(e, -inf) 使元素 e 成为根。现在我们执行 Pop()!同样,这项工作是由 Pop() 完成的。

Decrease-Key(e, v) 自己完成它的工作:它将 e 切割到根列表并开始切割级联以将其孩子也放入根列表(这也可以切割他们的子列表)。因此,Decrease-Key 将大量元素放入根列表中。你能猜出哪个函数必须解决这个问题吗?

TL;DR:Pop() 是斐波那契堆的工作马。所有其他操作都可以高效完成,因为它们为 Pop() 操作创建了工作。Pop() 收集工作并一次性执行(最多可能需要 O(n))。这实际上非常有效,因为“分组”的工作可以比单独的每个操作更快地完成。

所以是的,Pop() 占用你大部分时间是很自然的!

于 2015-09-11T15:26:29.857 回答
1

Fibanacci Heap 的 pop() 的摊销运行时间为 O(log n),最坏情况为 O(n)。如果你的堆很大,它很容易在你的算法中消耗大部分 CPU 时间,特别是因为你可能使用的大多数其他操作都有 O(1) 运行时(插入、顶部等)

我推荐的一件事是使用您喜欢的优化级别(例如 -O3)和调试信息(-g)尝试 callgrind,因为模板化的数据结构/容器(例如 fibonacci_heap)对内联函数的使用很重要。您测量的大多数 CPU 周期可能甚至不存在于优化的可执行文件中。

于 2014-02-17T01:59:43.267 回答