【问题标题】:How to improve Boost Fibonacci Heap performance如何提高 Boost Fibonacci Heap 性能
【发布时间】:2014-03-14 22:33:36
【问题描述】:

我正在实现快速行进算法,这是某种连续的 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_;

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

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

谢谢!

【问题讨论】:

标签: c++ boost fibonacci-heap


【解决方案1】:

其他答案并没有提到重要的部分:当然 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() 占用你大部分时间是很自然的!

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的回答。我不完全确定 pop() 函数中的 O(log n) 是什么意思,但你的评论帮助了我。现在我正在尝试使用 -O3 参数进行编译,但我遇到了调试到发布的差距stackoverflow.com/questions/21751553/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    相关资源
    最近更新 更多