Skip to content

Commit

Permalink
[libc++] "Bottom-up heapsort" improvement to sort_heap.
Browse files Browse the repository at this point in the history
https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort
In `pop_heap` specifically, the item we insert at the top and
sift downward is guaranteed to be leaf-sized, so we expect it
to go pretty far down. Sift it down as if it were INT_MIN, and
then bubble it back up if needed.
Also known as "heapsort with bounce."

Numbers are here: https://godbolt.org/z/cvfnYW6fe

Fixes #10008.

Differential Revision: https://reviews.llvm.org/D118003
  • Loading branch information
Arthur O'Dwyer committed Mar 8, 2022
1 parent e3d3755 commit 79d08e3
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 5 deletions.
5 changes: 5 additions & 0 deletions libcxx/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ New Features
------------

- Implemented P0627R6 (Function to mark unreachable code)

- Implemented P1165R1 (Make stateful allocator propagation more consistent for ``operator+(basic_string)``)

- `pop_heap` now uses an algorithm known as "bottom-up heapsort" or
"heapsort with bounce" to reduce the number of comparisons, and rearranges
elements using move-assignment instead of `swap`.

API Changes
-----------

Expand Down
18 changes: 15 additions & 3 deletions libcxx/include/__algorithm/pop_heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

#include <__algorithm/comp.h>
#include <__algorithm/comp_ref_type.h>
#include <__algorithm/push_heap.h>
#include <__algorithm/sift_down.h>
#include <__config>
#include <__iterator/iterator_traits.h>
#include <__utility/swap.h>
#include <__utility/move.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
Expand All @@ -28,10 +29,21 @@ void
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len)
{
using value_type = typename iterator_traits<_RandomAccessIterator>::value_type;

if (__len > 1)
{
swap(*__first, *--__last);
_VSTD::__sift_down<_Compare>(__first, __comp, __len - 1, __first);
value_type __top = std::move(*__first); // create a hole at __first
_RandomAccessIterator __hole = std::__floyd_sift_down<_Compare>(__first, __comp, __len);
--__last;
if (__hole == __last) {
*__hole = std::move(__top);
} else {
*__hole = std::move(*__last);
++__hole;
*__last = std::move(__top);
std::__sift_up<_Compare>(__first, __hole, __comp, __hole - __first);
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions libcxx/include/__algorithm/sift_down.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#ifndef _LIBCPP___ALGORITHM_SIFT_DOWN_H
#define _LIBCPP___ALGORITHM_SIFT_DOWN_H

#include <__assert>
#include <__config>
#include <__iterator/iterator_traits.h>
#include <__utility/move.h>
Expand Down Expand Up @@ -73,6 +74,38 @@ __sift_down(_RandomAccessIterator __first, _Compare __comp,
*__start = _VSTD::move(__top);
}

template <class _Compare, class _RandomAccessIterator>
_LIBCPP_CONSTEXPR_AFTER_CXX11 _RandomAccessIterator
__floyd_sift_down(_RandomAccessIterator __first, _Compare __comp,
typename iterator_traits<_RandomAccessIterator>::difference_type __len)
{
using difference_type = typename iterator_traits<_RandomAccessIterator>::difference_type;
_LIBCPP_ASSERT(__len >= 2, "shouldn't be called unless __len >= 2");

_RandomAccessIterator __hole = __first;
_RandomAccessIterator __child_i = __first;
difference_type __child = 0;

while (true) {
__child_i += difference_type(__child + 1);
__child = 2 * __child + 1;

if ((__child + 1) < __len && __comp(*__child_i, *(__child_i + difference_type(1)))) {
// right-child exists and is greater than left-child
++__child_i;
++__child;
}

// swap __hole with its largest child
*__hole = std::move(*__child_i);
__hole = std::move(__child_i);

// if __hole is now a leaf, we're done
if (__child > (__len - 2) / 2)
return __hole;
}
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___ALGORITHM_SIFT_DOWN_H
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ int main(int, char**)
stats = {};
std::sort_heap(v.begin(), v.end());
assert(stats.copied == 0);
assert(stats.moved == 1'900'731);
assert(stats.moved == 1'764'997);
#ifndef _LIBCPP_DEBUG
assert(stats.compared == 2'831'757);
assert(stats.compared == 1'534'701);
#endif

assert(std::is_sorted(v.begin(), v.end()));
Expand Down

0 comments on commit 79d08e3

Please sign in to comment.