[libcxx-commits] [libcxx] 1edc723 - Checked that complexity of std::sort_heap is 2N log(N) comparisons
Nilay Vaish via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Mar 6 08:42:38 PST 2023
Author: Nilay Vaish
Date: 2023-03-06T08:42:25-08:00
New Revision: 1edc72385a55adabc2803a0cde282c2d28785581
URL: https://github.com/llvm/llvm-project/commit/1edc72385a55adabc2803a0cde282c2d28785581
DIFF: https://github.com/llvm/llvm-project/commit/1edc72385a55adabc2803a0cde282c2d28785581.diff
LOG: Checked that complexity of std::sort_heap is 2N log(N) comparisons
https://wg21.link/LWG2444 updated the comparison complexity of
std:sort_heap to be at most 2N log (N) where N == last - first. In the
current implementation, we invoke __pop_heap exactly N-1 times. In each
call to __pop_heap, we first go down the heap from first to possibly
last in the function __floyd_sift_down. Then, we possibly go back up in
the function __sift_up.
In the function __floyd_sift_down, there is loop in which one comparison
is made in each iteration. The loop runs till __child becomes greater
than (__len - 2) / 2. __child starts at 0 and it is at least set to 2 *
__child + 1 on each iteration. Thus, after k iterations, __child will
be at least 2^k - 1. After log(N) iterations, __child >= 2^(log(N)) -
1 = N - 1 > (__len - 2) / 2. This means that the while loop in the
function __floyd_sift_down would perform at most log(N) comparisons on
each invocation.
In the function __sift_up, there is one comparison made that will almost
always occur. After that there is a do-while loop. The comparison
function is invoked once in each iteration. In the worst case, the loop
will run till __len goes down to zero. It can start from (N-3)/2. In
each iteration, __len goes down to (__len-1) / 2. After k iterations,
__len will be at most (N - 2^(k+1) -1) / 2^(k+1). Thus, __len will
become when (N-2^(k+1)-1) < 2^(k+1) i.e. N < 2^(k+2) + 1. This means
at most log(N) - 1 iterations for the loop. So in total at most log(N)
comparison will be performed in __sift_up.
So overall for each iteration of the loop in __pop_heap, there will at
most 2 log(N) comparisons. So, the total number of comparisons is
at most 2 N log(N).
We also updated the test sort.heap/complexity.pass.cpp to test for the
number of operations.
Differential Revision: https://reviews.llvm.org/D144538
Added:
libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
Modified:
libcxx/docs/Status/Cxx20Issues.csv
libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp
Removed:
libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
################################################################################
diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index c2cee7e88b83a..062eb769f34cd 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -1,6 +1,6 @@
"Issue #","Issue Name","Meeting","Status","First released version","Labels"
"`2070 <https://wg21.link/LWG2070>`__","``allocate_shared``\ should use ``allocator_traits<A>::construct``\ ","Toronto","Resolved by `P0674R1 <https://wg21.link/P0674R1>`__",""
-"`2444 <https://wg21.link/LWG2444>`__","Inconsistent complexity for ``std::sort_heap``\ ","Toronto","",""
+"`2444 <https://wg21.link/LWG2444>`__","Inconsistent complexity for ``std::sort_heap``\ ","Toronto","|Nothing To Do|",""
"`2593 <https://wg21.link/LWG2593>`__","Moved-from state of Allocators","Toronto","",""
"`2597 <https://wg21.link/LWG2597>`__","``std::log``\ misspecified for complex numbers","Toronto","",""
"`2783 <https://wg21.link/LWG2783>`__","``stack::emplace()``\ and ``queue::emplace()``\ should return ``decltype(auto)``\ ","Toronto","|Complete|",""
diff --git a/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
similarity index 63%
rename from libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
rename to libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
index 0ed37a2b920e9..e3cb2339fddde 100644
--- a/libcxx/test/libcxx/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/complexity.pass.cpp
@@ -21,8 +21,8 @@
struct Stats {
int compared = 0;
- int copied = 0;
- int moved = 0;
+ int copied = 0;
+ int moved = 0;
} stats;
struct MyInt {
@@ -46,30 +46,30 @@ struct MyInt {
}
};
-int main(int, char**)
-{
- const int N = 100'000;
+int main(int, char**) {
+ constexpr int N = (1 << 20);
std::vector<MyInt> v;
v.reserve(N);
std::mt19937 g;
- for (int i = 0; i < N; ++i)
- v.emplace_back(g());
- std::make_heap(v.begin(), v.end());
-
- // The exact stats of our current implementation are recorded here.
- // If something changes to make them go a bit up or down, that's probably fine,
- // and we can just update this test.
- // But if they suddenly leap upward, that's a bad thing.
-
- stats = {};
- std::sort_heap(v.begin(), v.end());
- assert(stats.copied == 0);
- assert(stats.moved == 1'764'997);
+ for (int i = 0; i < N; ++i) {
+ v.emplace_back(i);
+ }
+ for (int logn = 10; logn <= 20; ++logn) {
+ const int n = (1 << logn);
+ auto first = v.begin();
+ auto last = v.begin() + n;
+ std::shuffle(first, last, g);
+ std::make_heap(first, last);
+ // The exact stats of our current implementation are recorded here.
+ stats = {};
+ std::sort_heap(first, last);
+ LIBCPP_ASSERT(stats.copied == 0);
+ LIBCPP_ASSERT(stats.moved <= 2 * n + n * logn);
#ifndef _LIBCPP_ENABLE_DEBUG_MODE
- assert(stats.compared == 1'534'701);
+ LIBCPP_ASSERT(stats.compared <= n * logn);
#endif
-
- assert(std::is_sorted(v.begin(), v.end()));
-
+ LIBCPP_ASSERT(std::is_sorted(first, last));
+ LIBCPP_ASSERT(stats.compared <= 2 * n * logn);
+ }
return 0;
}
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp
index faf1e2309eb67..abf317a753406 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.heap.operations/sort.heap/ranges_sort_heap.pass.cpp
@@ -25,6 +25,7 @@
#include <array>
#include <concepts>
#include <functional>
+#include <random>
#include <ranges>
#include "almost_satisfies_types.h"
@@ -212,9 +213,63 @@ constexpr bool test() {
return true;
}
+struct Stats {
+ int compared = 0;
+ int copied = 0;
+ int moved = 0;
+} stats;
+
+struct MyInt {
+ int value;
+ explicit MyInt(int xval) : value(xval) {}
+ MyInt(const MyInt& other) : value(other.value) { ++stats.copied; }
+ MyInt(MyInt&& other) : value(other.value) { ++stats.moved; }
+ MyInt& operator=(const MyInt& other) {
+ value = other.value;
+ ++stats.copied;
+ return *this;
+ }
+ MyInt& operator=(MyInt&& other) {
+ value = other.value;
+ ++stats.moved;
+ return *this;
+ }
+ static bool Comp(const MyInt& a, const MyInt& b) {
+ ++stats.compared;
+ return a.value < b.value;
+ }
+};
+
+void test_complexity() {
+ constexpr int N = (1 << 20);
+ std::vector<MyInt> v;
+ v.reserve(N);
+ std::mt19937 g;
+ for (int i = 0; i < N; ++i) {
+ v.emplace_back(i);
+ }
+ for (int logn = 10; logn <= 20; ++logn) {
+ const int n = (1 << logn);
+ auto first = v.begin();
+ auto last = v.begin() + n;
+ std::shuffle(first, last, g);
+ std::make_heap(first, last, &MyInt::Comp);
+ // The exact stats of our current implementation are recorded here.
+ stats = {};
+ std::ranges::sort_heap(first, last, &MyInt::Comp);
+ LIBCPP_ASSERT(stats.copied == 0);
+ LIBCPP_ASSERT(stats.moved <= 2 * n + n * logn);
+#ifndef _LIBCPP_ENABLE_DEBUG_MODE
+ LIBCPP_ASSERT(stats.compared <= n * logn);
+#endif
+ LIBCPP_ASSERT(std::is_sorted(first, last, &MyInt::Comp));
+ LIBCPP_ASSERT(stats.compared <= 2 * n * logn);
+ }
+}
+
int main(int, char**) {
test();
static_assert(test());
-
+ test_complexity();
return 0;
}
More information about the libcxx-commits
mailing list