Bja*_*une 50 c++ performance computer-science priority-queue data-structures
我正在寻找有关如何有效实现二进制堆的信息.我觉得应该有一篇关于有效实现堆的好文章,但我还没找到.事实上,我已经无法找到任何有关超出基础知识的有效实现的资源,例如将堆存储在数组中.我正在寻找制作快速二进制堆的技术,超出我在下面描述的那些.
我已经编写了一个比Microsoft Visual C++和GCC的std :: priority_queue或使用std :: make_heap,std :: push_heap和std :: pop_heap更快的C++实现.以下是我在实现中已经涵盖的技术.我自己只想出了最后两个,尽管我怀疑这些是新想法:
(编辑:添加了关于内存优化的部分)
相反,我们可以在顶部元素所在的堆中留一个洞.然后我们通过迭代地移动较大的孩子来将那个洞向下移动.这需要我们经过的每个级别只有1个比较.通过这种方式,洞将成为一片叶子.此时,我们可以将最右下方的叶子移动到孔的位置,并将该值向上移动,直到堆属性恢复为止.由于我们移动的值是一片叶子,我们不希望它在树上移动很远.所以我们应该期待比log n更多的比较,这比以前更好.
&(p[i]) == static_cast<char*>(p) + sizeof(T) * i
Run Code Online (Sandbox Code Playgroud)
并且编译器必须执行此计算以获得p [i].sizeof(T)是编译时常量,如果sizeof(T)是2的幂,则可以更有效地进行乘法.通过添加8个填充字节以将sizeof(T)从24增加到32,我的实现变得更快.降低缓存效率可能意味着这对于足够大的数据集来说并不是一个胜利.
&(p[i]) == static_cast<char*>(p) + sizeof(T) * i == static_cast<char*>(p) + j
Run Code Online (Sandbox Code Playgroud)
然后,j值的左子和右子公式分别变为2*j和2*j + sizeof(T).父公式有点棘手,除了将j值转换为i值之外,我还没有找到方法做到这一点,如下所示:
parentOnJ(j) = parent(j/sizeof(T))*sizeof(T) == (j/(2*sizeof(T))*sizeof(T)
Run Code Online (Sandbox Code Playgroud)
如果sizeof(T)是2的幂,那么这将编译为2个移位.这比使用索引i的普通父亲多1次操作.然而,我们然后在查找上保存1个操作.因此,净效应是找到父母用这种方式花费相同的时间,而左子和右子的查找变得更快.
TokenMacGuy和templatetypedef的答案指出了基于内存的优化,可以减少缓存未命中.对于非常大的数据集或不常使用的优先级队列,可以通过OS将队列的一部分交换到磁盘.在这种情况下,为了最佳地使用缓存而增加大量开销是值得的,因为从磁盘进行交换非常慢.我的数据很容易适应内存并且可以连续使用,因此队列的任何部分都不会被交换到磁盘.我怀疑大多数优先级队列的使用都是这种情况.
还有其他优先级队列旨在更好地利用CPU缓存.例如,4堆应该具有更少的缓存未命中,并且额外开销的数量不是那么多.LaMarca和Ladner在1996年报告说,他们从一致的4堆中获得了75%的性能提升.但是,Hendriks在2010年报告说:
还测试了LaMarca和Ladner [17]提出的改进数据局部性和减少缓存未命中的隐式堆的改进.我们实现了一个四向堆,对于非常偏斜的输入数据,它确实显示出比双向堆稍微更好的一致性,但仅适用于非常大的队列大小.分层堆可以更好地处理非常大的队列大小.
关于该主题的一篇有趣的论文/文章考虑了缓存/分页对堆的整体布局的行为; 这个想法是,与数据结构的实现几乎任何其他部分相比,支付缓存未命中或页面的成本要高得多.本文讨论了解决此问题的堆布局.
| 归档时间: |
|
| 查看次数: |
9996 次 |
| 最近记录: |