[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