From: Patrick Palka <ppalka@redhat.com>
To: gcc-patches@gcc.gnu.org
Cc: libstdc++@gcc.gnu.org, Patrick Palka <ppalka@redhat.com>
Subject: [PATCH] libstdc++: Reduce ranges::minmax/minmax_element comparison complexity
Date: Tue, 4 May 2021 21:42:20 -0400 [thread overview]
Message-ID: <20210505014220.1216746-1-ppalka@redhat.com> (raw)
This rewrites ranges::minmax and ranges::minmax_element so that it
performs at most 3*N/2 many comparisons, as required by the standard.
In passing, this also fixes PR100387 by avoiding a premature std::move
in ranges::minmax and in std::shift_right.
Tested on x86_64-pc-linux-gnu, does this look OK for trunk and perhaps
10/11?
libstdc++-v3/ChangeLog:
PR libstdc++/100387
* include/bits/ranges_algo.h (__minmax_fn::operator()): Rewrite
to limit comparison complexity to 3*N/2. Avoid premature std::move.
(__minmax_element_fn::operator()): Likewise.
(shift_right): Avoid premature std::move of __result.
* testsuite/25_algorithms/minmax/constrained.cc (test04, test05):
New tests.
* testsuite/25_algorithms/minmax_element/constrained.cc (test02):
Likewise.
---
libstdc++-v3/include/bits/ranges_algo.h | 87 ++++++++++++++-----
.../25_algorithms/minmax/constrained.cc | 31 +++++++
.../minmax_element/constrained.cc | 19 ++++
3 files changed, 113 insertions(+), 24 deletions(-)
diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h
index cda3042c11f..bbd29127e89 100644
--- a/libstdc++-v3/include/bits/ranges_algo.h
+++ b/libstdc++-v3/include/bits/ranges_algo.h
@@ -3291,18 +3291,39 @@ namespace ranges
auto __first = ranges::begin(__r);
auto __last = ranges::end(__r);
__glibcxx_assert(__first != __last);
+ auto __comp_proj = __detail::__make_comp_proj(__comp, __proj);
minmax_result<range_value_t<_Range>> __result = {*__first, *__first};
while (++__first != __last)
{
- auto __tmp = *__first;
- if (std::__invoke(__comp,
- std::__invoke(__proj, __tmp),
- std::__invoke(__proj, __result.min)))
- __result.min = std::move(__tmp);
- if (!(bool)std::__invoke(__comp,
- std::__invoke(__proj, __tmp),
- std::__invoke(__proj, __result.max)))
- __result.max = std::move(__tmp);
+ // Process two elements at a time so that we perform at most
+ // 3*N/2 many comparisons in total (each of the N/2 iterations
+ // of this loop performs three comparisions).
+ auto __val1 = *__first;
+ if (++__first == __last)
+ {
+ // N is odd; in this final iteration, we perform a just one
+ // comparison, for a total of 3*(N-1)/2 + 1 < 3*N/2 comparisons.
+ if (__comp_proj(__val1, __result.min))
+ __result.min = std::move(__val1);
+ else if (!__comp_proj(__val1, __result.max))
+ __result.max = std::move(__val1);
+ break;
+ }
+ auto __val2 = *__first;
+ if (!__comp_proj(__val2, __val1))
+ {
+ if (__comp_proj(__val1, __result.min))
+ __result.min = std::move(__val1);
+ if (!__comp_proj(__val2, __result.max))
+ __result.max = std::move(__val2);
+ }
+ else
+ {
+ if (__comp_proj(__val2, __result.min))
+ __result.min = std::move(__val2);
+ if (!__comp_proj(__val1, __result.max))
+ __result.max = std::move(__val1);
+ }
}
return __result;
}
@@ -3408,21 +3429,40 @@ namespace ranges
operator()(_Iter __first, _Sent __last,
_Comp __comp = {}, _Proj __proj = {}) const
{
- if (__first == __last)
- return {__first, __first};
-
+ auto __comp_proj = __detail::__make_comp_proj(__comp, __proj);
minmax_element_result<_Iter> __result = {__first, __first};
- auto __i = __first;
- while (++__i != __last)
+ if (__first == __last)
+ return __result;
+ while (++__first != __last)
{
- if (std::__invoke(__comp,
- std::__invoke(__proj, *__i),
- std::__invoke(__proj, *__result.min)))
- __result.min = __i;
- if (!(bool)std::__invoke(__comp,
- std::__invoke(__proj, *__i),
- std::__invoke(__proj, *__result.max)))
- __result.max = __i;
+ // Process two elements at a time so that we perform at most
+ // 3*N/2 many comparisons in total (each of the N/2 iterations
+ // of this loop performs three comparisions).
+ auto __prev = __first;
+ if (++__first == __last)
+ {
+ // N is odd; in this final iteration, we perform a just one
+ // comparison, for a total of 3*(N-1)/2 + 1 < 3*N/2 comparisons.
+ if (__comp_proj(*__prev, *__result.min))
+ __result.min = __prev;
+ else if (!__comp_proj(*__prev, *__result.max))
+ __result.max = __prev;
+ break;
+ }
+ if (!__comp_proj(*__first, *__prev))
+ {
+ if (__comp_proj(*__prev, *__result.min))
+ __result.min = __prev;
+ if (!__comp_proj(*__first, *__result.max))
+ __result.max = __first;
+ }
+ else
+ {
+ if (__comp_proj(*__first, *__result.min))
+ __result.min = __first;
+ if (!__comp_proj(*__prev, *__result.max))
+ __result.max = __prev;
+ }
}
return __result;
}
@@ -3749,8 +3789,7 @@ namespace ranges
// i.e. we are shifting out at least half of the range. In
// this case we can safely perform the shift with a single
// move.
- std::move(std::move(__first), std::move(__dest_head),
- std::move(__result));
+ std::move(std::move(__first), std::move(__dest_head), __result);
return __result;
}
++__dest_head;
diff --git a/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc
index 786922414b5..2373c15a9d2 100644
--- a/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc
+++ b/libstdc++-v3/testsuite/25_algorithms/minmax/constrained.cc
@@ -19,6 +19,8 @@
// { dg-do run { target c++2a } }
#include <algorithm>
+#include <string>
+#include <vector>
#include <testsuite_hooks.h>
#include <testsuite_iterators.h>
@@ -89,10 +91,39 @@ test03()
== res_t(1,4) );
}
+void
+test04()
+{
+ // Verify we perform most 3*N/2 applications of the comparison predicate.
+ static int counter;
+ struct counted_less
+ { bool operator()(int a, int b) { ++counter; return a < b; } };
+
+ ranges::minmax({1,2,3,4,5,6,7,8,9,10}, counted_less{});
+ VERIFY( counter <= 15 );
+
+ counter = 0;
+ ranges::minmax({10,9,8,7,6,5,4,3,2,1}, counted_less{});
+ VERIFY( counter <= 15 );
+}
+
+void
+test05()
+{
+ // PR libstdc++/100387
+ std::vector<std::string> v{"b", "a"};
+ auto [min, max] = ranges::minmax(v, [](const auto& a, const auto& b) {
+ return a.size() == b.size() ? a.front() < b.front() : a.size() > b.size();
+ });
+ VERIFY( min == "a" && max == "b" );
+}
+
int
main()
{
test01();
test02();
test03();
+ test04();
+ test05();
}
diff --git a/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc b/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc
index 3b11c0dd96c..89044181530 100644
--- a/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc
+++ b/libstdc++-v3/testsuite/25_algorithms/minmax_element/constrained.cc
@@ -61,8 +61,27 @@ test01()
static_assert(ranges::minmax_element(y, y+3, {}, &X::i).max->j == 3);
}
+void
+test02()
+{
+ // Verify we perform most 3*N/2 applications of the comparison predicate.
+ static int counter;
+ struct counted_less
+ { bool operator()(int a, int b) { ++counter; return a < b; } };
+
+ int x[] = {1,2,3,4,5,6,7,8,9,10};
+ ranges::minmax(x, counted_less{});
+ VERIFY( counter <= 15 );
+
+ ranges::reverse(x);
+ counter = 0;
+ ranges::minmax(x, counted_less{});
+ VERIFY( counter <= 15 );
+}
+
int
main()
{
test01();
+ test02();
}
--
2.31.1.442.g7e39198978
next reply other threads:[~2021-05-05 1:42 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-05-05 1:42 Patrick Palka [this message]
2021-05-05 9:39 ` Jonathan Wakely
2021-05-05 9:46 ` Jonathan Wakely
2021-05-05 15:33 ` Patrick Palka
2021-05-05 15:48 ` Patrick Palka
2021-06-18 17:44 ` Patrick Palka
2021-06-18 21:19 ` Jonathan Wakely
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210505014220.1216746-1-ppalka@redhat.com \
--to=ppalka@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=libstdc++@gcc.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).