Skip to content

Commit 86967cb

Browse files
committed
[libc++] Forward to memcmp in lexicographical_compare
1 parent fe07d9a commit 86967cb

File tree

13 files changed

+211
-90
lines changed

13 files changed

+211
-90
lines changed

libcxx/benchmarks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ set(BENCHMARK_TESTS
116116
algorithms/find.bench.cpp
117117
algorithms/fill.bench.cpp
118118
algorithms/for_each.bench.cpp
119+
algorithms/lexicographical_compare.bench.cpp
119120
algorithms/lower_bound.bench.cpp
120121
algorithms/make_heap.bench.cpp
121122
algorithms/make_heap_then_sort_heap.bench.cpp
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <algorithm>
10+
#include <benchmark/benchmark.h>
11+
#include <vector>
12+
13+
// Benchmarks the worst case: check the whole range just to find out that they compare equal
14+
template <class T>
15+
static void bm_lexicographical_compare(benchmark::State& state) {
16+
std::vector<T> vec1(state.range(), '1');
17+
std::vector<T> vec2(state.range(), '1');
18+
19+
for (auto _ : state) {
20+
benchmark::DoNotOptimize(vec1);
21+
benchmark::DoNotOptimize(vec2);
22+
benchmark::DoNotOptimize(std::lexicographical_compare(vec1.begin(), vec1.end(), vec2.begin(), vec2.end()));
23+
}
24+
}
25+
BENCHMARK(bm_lexicographical_compare<unsigned char>)->DenseRange(1, 8)->Range(16, 1 << 20);
26+
BENCHMARK(bm_lexicographical_compare<signed char>)->DenseRange(1, 8)->Range(16, 1 << 20);
27+
BENCHMARK(bm_lexicographical_compare<int>)->DenseRange(1, 8)->Range(16, 1 << 20);
28+
29+
template <class T>
30+
static void bm_ranges_lexicographical_compare(benchmark::State& state) {
31+
std::vector<T> vec1(state.range(), '1');
32+
std::vector<T> vec2(state.range(), '1');
33+
34+
for (auto _ : state) {
35+
benchmark::DoNotOptimize(vec1);
36+
benchmark::DoNotOptimize(vec2);
37+
benchmark::DoNotOptimize(std::ranges::lexicographical_compare(vec1.begin(), vec1.end(), vec2.begin(), vec2.end()));
38+
}
39+
}
40+
BENCHMARK(bm_ranges_lexicographical_compare<unsigned char>)->DenseRange(1, 8)->Range(16, 1 << 20);
41+
BENCHMARK(bm_ranges_lexicographical_compare<signed char>)->DenseRange(1, 8)->Range(16, 1 << 20);
42+
BENCHMARK(bm_ranges_lexicographical_compare<int>)->DenseRange(1, 8)->Range(16, 1 << 20);
43+
44+
BENCHMARK_MAIN();

libcxx/include/__algorithm/comp.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include <__config>
1313
#include <__type_traits/desugars_to.h>
14+
#include <__type_traits/is_integral.h>
1415

1516
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
1617
# pragma GCC system_header
@@ -42,7 +43,7 @@ struct __less<void, void> {
4243
};
4344

4445
template <class _Tp>
45-
inline const bool __desugars_to_v<__less_tag, __less<>, _Tp, _Tp> = true;
46+
inline const bool __desugars_to_v<__totally_ordered_less_tag, __less<>, _Tp, _Tp> = is_integral<_Tp>::value;
4647

4748
_LIBCPP_END_NAMESPACE_STD
4849

libcxx/include/__algorithm/lexicographical_compare.h

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,101 @@
1111

1212
#include <__algorithm/comp.h>
1313
#include <__algorithm/comp_ref_type.h>
14+
#include <__algorithm/min.h>
15+
#include <__algorithm/mismatch.h>
16+
#include <__algorithm/simd_utils.h>
17+
#include <__algorithm/unwrap_iter.h>
1418
#include <__config>
19+
#include <__functional/identity.h>
1520
#include <__iterator/iterator_traits.h>
21+
#include <__string/constexpr_c_functions.h>
22+
#include <__type_traits/desugars_to.h>
23+
#include <__type_traits/invoke.h>
24+
#include <__type_traits/is_equality_comparable.h>
25+
#include <__type_traits/is_integral.h>
26+
#include <__type_traits/is_trivially_lexicographically_comparable.h>
27+
#include <__type_traits/is_volatile.h>
28+
#include <cwchar>
1629

1730
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
1831
# pragma GCC system_header
1932
#endif
2033

34+
_LIBCPP_PUSH_MACROS
35+
#include <__undef_macros>
36+
2137
_LIBCPP_BEGIN_NAMESPACE_STD
2238

23-
template <class _Compare, class _InputIterator1, class _InputIterator2>
39+
template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Proj1, class _Proj2, class _Comp>
2440
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __lexicographical_compare(
25-
_InputIterator1 __first1,
26-
_InputIterator1 __last1,
27-
_InputIterator2 __first2,
28-
_InputIterator2 __last2,
29-
_Compare __comp) {
30-
for (; __first2 != __last2; ++__first1, (void)++__first2) {
31-
if (__first1 == __last1 || __comp(*__first1, *__first2))
41+
_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2, _Comp& __comp, _Proj1& __proj1, _Proj2& __proj2) {
42+
while (__first2 != __last2) {
43+
if (__first1 == __last1 ||
44+
std::__invoke(__comp, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
3245
return true;
33-
if (__comp(*__first2, *__first1))
46+
if (std::__invoke(__comp, std::__invoke(__proj2, *__first2), std::__invoke(__proj1, *__first1)))
3447
return false;
48+
++__first1;
49+
++__first2;
3550
}
3651
return false;
3752
}
3853

54+
#ifndef _LIBCPP_CXX03_LANG
55+
56+
template <class _Tp,
57+
class _Proj1,
58+
class _Proj2,
59+
class _Comp,
60+
__enable_if_t<__desugars_to_v<__totally_ordered_less_tag, _Comp, _Tp, _Tp> && !is_volatile<_Tp>::value &&
61+
__libcpp_is_trivially_equality_comparable<_Tp, _Tp>::value &&
62+
__is_identity<_Proj1>::value && __is_identity<_Proj2>::value,
63+
int> = 0>
64+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
65+
__lexicographical_compare(_Tp* __first1, _Tp* __last1, _Tp* __first2, _Tp* __last2, _Comp&, _Proj1&, _Proj2&) {
66+
if constexpr (__is_trivially_lexicographically_comparable_v<_Tp, _Tp>) {
67+
auto __res =
68+
std::__constexpr_memcmp(__first1, __first2, __element_count(std::min(__last1 - __first1, __last2 - __first2)));
69+
if (__res == 0)
70+
return __last1 - __first1 < __last2 - __first2;
71+
return __res < 0;
72+
}
73+
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
74+
else if constexpr (is_same<__remove_cv_t<_Tp>, wchar_t>::value) {
75+
auto __res = std::__constexpr_wmemcmp(__first1, __first2, std::min(__last1 - __first1, __last2 - __first2));
76+
if (__res == 0)
77+
return __last1 - __first1 < __last2 - __first2;
78+
return __res < 0;
79+
}
80+
#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
81+
else {
82+
auto __res = std::mismatch(__first1, __last1, __first2, __last2);
83+
if (__res.second == __last2)
84+
return false;
85+
if (__res.first == __last1)
86+
return true;
87+
return *__res.first < *__res.second;
88+
}
89+
}
90+
91+
#endif // _LIBCPP_CXX03_LANG
92+
3993
template <class _InputIterator1, class _InputIterator2, class _Compare>
4094
_LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool lexicographical_compare(
4195
_InputIterator1 __first1,
4296
_InputIterator1 __last1,
4397
_InputIterator2 __first2,
4498
_InputIterator2 __last2,
4599
_Compare __comp) {
46-
return std::__lexicographical_compare<__comp_ref_type<_Compare> >(__first1, __last1, __first2, __last2, __comp);
100+
__identity __proj;
101+
return std::__lexicographical_compare(
102+
std::__unwrap_iter(__first1),
103+
std::__unwrap_iter(__last1),
104+
std::__unwrap_iter(__first2),
105+
std::__unwrap_iter(__last2),
106+
__comp,
107+
__proj,
108+
__proj);
47109
}
48110

49111
template <class _InputIterator1, class _InputIterator2>
@@ -54,4 +116,6 @@ _LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 boo
54116

55117
_LIBCPP_END_NAMESPACE_STD
56118

119+
_LIBCPP_POP_MACROS
120+
57121
#endif // _LIBCPP___ALGORITHM_LEXICOGRAPHICAL_COMPARE_H

libcxx/include/__algorithm/ranges_lexicographical_compare.h

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#ifndef _LIBCPP___ALGORITHM_RANGES_LEXICOGRAPHICAL_COMPARE_H
1010
#define _LIBCPP___ALGORITHM_RANGES_LEXICOGRAPHICAL_COMPARE_H
1111

12+
#include <__algorithm/lexicographical_compare.h>
13+
#include <__algorithm/unwrap_range.h>
1214
#include <__config>
1315
#include <__functional/identity.h>
1416
#include <__functional/invoke.h>
@@ -34,23 +36,24 @@ namespace ranges {
3436
namespace __lexicographical_compare {
3537
struct __fn {
3638
template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Proj1, class _Proj2, class _Comp>
37-
_LIBCPP_HIDE_FROM_ABI constexpr static bool __lexicographical_compare_impl(
39+
static _LIBCPP_HIDE_FROM_ABI constexpr bool __lexicographical_compare_unwrap(
3840
_Iter1 __first1,
3941
_Sent1 __last1,
4042
_Iter2 __first2,
4143
_Sent2 __last2,
4244
_Comp& __comp,
4345
_Proj1& __proj1,
4446
_Proj2& __proj2) {
45-
while (__first2 != __last2) {
46-
if (__first1 == __last1 || std::invoke(__comp, std::invoke(__proj1, *__first1), std::invoke(__proj2, *__first2)))
47-
return true;
48-
if (std::invoke(__comp, std::invoke(__proj2, *__first2), std::invoke(__proj1, *__first1)))
49-
return false;
50-
++__first1;
51-
++__first2;
52-
}
53-
return false;
47+
auto [__first1_un, __last1_un] = std::__unwrap_range(std::move(__first1), std::move(__last1));
48+
auto [__first2_un, __last2_un] = std::__unwrap_range(std::move(__first2), std::move(__last2));
49+
return std::__lexicographical_compare(
50+
std::move(__first1_un),
51+
std::move(__last1_un),
52+
std::move(__first2_un),
53+
std::move(__last2_un),
54+
__comp,
55+
__proj1,
56+
__proj2);
5457
}
5558

5659
template <input_iterator _Iter1,
@@ -68,7 +71,7 @@ struct __fn {
6871
_Comp __comp = {},
6972
_Proj1 __proj1 = {},
7073
_Proj2 __proj2 = {}) const {
71-
return __lexicographical_compare_impl(
74+
return __lexicographical_compare_unwrap(
7275
std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2), __comp, __proj1, __proj2);
7376
}
7477

@@ -80,7 +83,7 @@ struct __fn {
8083
_Comp = ranges::less>
8184
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(
8285
_Range1&& __range1, _Range2&& __range2, _Comp __comp = {}, _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const {
83-
return __lexicographical_compare_impl(
86+
return __lexicographical_compare_unwrap(
8487
ranges::begin(__range1),
8588
ranges::end(__range1),
8689
ranges::begin(__range2),

libcxx/include/__algorithm/ranges_minmax.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ struct __fn {
8989
// vectorize the code.
9090
if constexpr (contiguous_range<_Range> && is_integral_v<_ValueT> &&
9191
__is_cheap_to_copy<_ValueT> & __is_identity<_Proj>::value &&
92-
__desugars_to_v<__less_tag, _Comp, _ValueT, _ValueT>) {
92+
__desugars_to_v<__totally_ordered_less_tag, _Comp, _ValueT, _ValueT>) {
9393
minmax_result<_ValueT> __result = {__r[0], __r[0]};
9494
for (auto __e : __r) {
9595
if (__e < __result.min)

libcxx/include/__functional/operations.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <__functional/binary_function.h>
1515
#include <__functional/unary_function.h>
1616
#include <__type_traits/desugars_to.h>
17+
#include <__type_traits/is_integral.h>
1718
#include <__utility/forward.h>
1819

1920
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -362,7 +363,7 @@ struct _LIBCPP_TEMPLATE_VIS less : __binary_function<_Tp, _Tp, bool> {
362363
_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(less);
363364

364365
template <class _Tp>
365-
inline const bool __desugars_to_v<__less_tag, less<_Tp>, _Tp, _Tp> = true;
366+
inline const bool __desugars_to_v<__totally_ordered_less_tag, less<_Tp>, _Tp, _Tp> = is_integral<_Tp>::value;
366367

367368
#if _LIBCPP_STD_VER >= 14
368369
template <>
@@ -377,7 +378,7 @@ struct _LIBCPP_TEMPLATE_VIS less<void> {
377378
};
378379

379380
template <class _Tp>
380-
inline const bool __desugars_to_v<__less_tag, less<>, _Tp, _Tp> = true;
381+
inline const bool __desugars_to_v<__totally_ordered_less_tag, less<>, _Tp, _Tp> = is_integral<_Tp>::value;
381382
#endif
382383

383384
#if _LIBCPP_STD_VER >= 14

libcxx/include/__functional/ranges_operations.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ template <class _Tp, class _Up>
100100
inline const bool __desugars_to_v<__equal_tag, ranges::equal_to, _Tp, _Up> = true;
101101

102102
template <class _Tp, class _Up>
103-
inline const bool __desugars_to_v<__less_tag, ranges::less, _Tp, _Up> = true;
103+
inline const bool __desugars_to_v<__totally_ordered_less_tag, ranges::less, _Tp, _Up> = true;
104104

105105
#endif // _LIBCPP_STD_VER >= 20
106106

libcxx/include/__string/constexpr_c_functions.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 size_t __constexpr_st
6464
return __builtin_strlen(reinterpret_cast<const char*>(__str));
6565
}
6666

67-
// Because of __libcpp_is_trivially_lexicographically_comparable we know that comparing the object representations is
67+
// Because of __is_trivially_lexicographically_comparable_v we know that comparing the object representations is
6868
// equivalent to a std::memcmp. Since we have multiple objects contiguously in memory, we can call memcmp once instead
6969
// of invoking it on every object individually.
7070
template <class _Tp, class _Up>
7171
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 int
7272
__constexpr_memcmp(const _Tp* __lhs, const _Up* __rhs, __element_count __n) {
73-
static_assert(__libcpp_is_trivially_lexicographically_comparable<_Tp, _Up>::value,
73+
static_assert(__is_trivially_lexicographically_comparable_v<_Tp, _Up>,
7474
"_Tp and _Up have to be trivially lexicographically comparable");
7575

7676
auto __count = static_cast<size_t>(__n);

libcxx/include/__type_traits/desugars_to.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,19 @@
1717

1818
_LIBCPP_BEGIN_NAMESPACE_STD
1919

20-
// Tags to represent the canonical operations
20+
// Tags to represent the canonical operations.
21+
22+
// syntactically, the operation is equivalent to calling `a == b`
2123
struct __equal_tag {};
24+
25+
// syntactically, the operation is equivalent to calling `a + b`
2226
struct __plus_tag {};
23-
struct __less_tag {};
27+
28+
// syntactically, the operation is equivalent to calling `a < b`, and these expressions
29+
// have to be true for any `a` and `b`:
30+
// - `(a < b) == (b > a)`
31+
// - `(!(a < b) && !(b < a)) == (a == b)`
32+
struct __totally_ordered_less_tag {};
2433

2534
// This class template is used to determine whether an operation "desugars"
2635
// (or boils down) to a given canonical operation.

libcxx/include/__type_traits/is_trivially_lexicographically_comparable.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <__type_traits/remove_cv.h>
1717
#include <__type_traits/void_t.h>
1818
#include <__utility/declval.h>
19+
#include <cstddef>
1920

2021
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2122
# pragma GCC system_header
@@ -40,13 +41,22 @@ _LIBCPP_BEGIN_NAMESPACE_STD
4041
// unsigned integer types with sizeof(T) > 1: depending on the endianness, the LSB might be the first byte to be
4142
// compared. This means that when comparing unsigned(129) and unsigned(2)
4243
// using memcmp(), the result would be that 2 > 129.
43-
// TODO: Do we want to enable this on big-endian systems?
44+
45+
template <class _Tp>
46+
inline const bool __is_std_byte_v = false;
47+
48+
#if _LIBCPP_STD_VER >= 17
49+
template <>
50+
inline const bool __is_std_byte_v<byte> = true;
51+
#endif
4452

4553
template <class _Tp, class _Up>
46-
struct __libcpp_is_trivially_lexicographically_comparable
47-
: integral_constant<bool,
48-
is_same<__remove_cv_t<_Tp>, __remove_cv_t<_Up> >::value && sizeof(_Tp) == 1 &&
49-
is_unsigned<_Tp>::value> {};
54+
inline const bool __is_trivially_lexicographically_comparable_v =
55+
is_same<__remove_cv_t<_Tp>, __remove_cv_t<_Up> >::value &&
56+
#ifdef _LIBCPP_LITTLE_ENDIAN
57+
sizeof(_Tp) == 1 &&
58+
#endif
59+
(is_unsigned<_Tp>::value || __is_std_byte_v<_Tp>);
5060

5161
_LIBCPP_END_NAMESPACE_STD
5262

0 commit comments

Comments
 (0)