Skip to content

Commit 55e9c69

Browse files
committed
[libc++] Optimize std::find_if
1 parent 4ecfaa6 commit 55e9c69

File tree

10 files changed

+128
-58
lines changed

10 files changed

+128
-58
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ set(files
604604
__memory/unique_temporary_buffer.h
605605
__memory/uses_allocator.h
606606
__memory/uses_allocator_construction.h
607+
__memory/valid_range.h
607608
__memory_resource/memory_resource.h
608609
__memory_resource/monotonic_buffer_resource.h
609610
__memory_resource/polymorphic_allocator.h
@@ -924,7 +925,6 @@ set(files
924925
__utility/in_place.h
925926
__utility/integer_sequence.h
926927
__utility/is_pointer_in_range.h
927-
__utility/is_valid_range.h
928928
__utility/lazy_synth_three_way_comparator.h
929929
__utility/move.h
930930
__utility/no_destroy.h

libcxx/include/__algorithm/find_if.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#define _LIBCPP___ALGORITHM_FIND_IF_H
1212

1313
#include <__config>
14+
#include <__memory/valid_range.h>
1415

1516
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
1617
# pragma GCC system_header
@@ -21,6 +22,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
2122
template <class _InputIterator, class _Predicate>
2223
[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
2324
find_if(_InputIterator __first, _InputIterator __last, _Predicate __pred) {
25+
std::__assume_valid_range(__first, __last);
26+
2427
for (; __first != __last; ++__first)
2528
if (__pred(*__first))
2629
break;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
#ifndef _LIBCPP___MEMORY_VALID_RANGE_H
10+
#define _LIBCPP___MEMORY_VALID_RANGE_H
11+
12+
#include <__algorithm/comp.h>
13+
#include <__assert>
14+
#include <__config>
15+
#include <__iterator/iterator_traits.h>
16+
#include <__memory/assume_aligned.h>
17+
#include <__memory/pointer_traits.h>
18+
#include <__type_traits/is_constant_evaluated.h>
19+
#include <__type_traits/is_same.h>
20+
#include <__type_traits/remove_cvref.h>
21+
22+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
23+
# pragma GCC system_header
24+
#endif
25+
26+
_LIBCPP_BEGIN_NAMESPACE_STD
27+
28+
template <class _Tp>
29+
_LIBCPP_CONSTEXPR_SINCE_CXX14 _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") bool
30+
__is_valid_range(const _Tp* __first, const _Tp* __last) {
31+
if (__libcpp_is_constant_evaluated()) {
32+
// If this is not a constant during constant evaluation, that is because __first and __last are not
33+
// part of the same allocation. If they are part of the same allocation, we must still make sure they
34+
// are ordered properly.
35+
return __builtin_constant_p(__first <= __last) && __first <= __last;
36+
}
37+
38+
return !__less<>()(__last, __first);
39+
}
40+
41+
// This functions allows the compiler to assume that [__first, __last) is a valid range to be given to an algortihm.
42+
// Specifically, this means that
43+
// - [__first, __last) is dereferenceable
44+
// - __first and __last are correctly aligned according to the language rules
45+
// This allows (curently only clang-based compilers) to auto-vectorize algorithms that contain early returns.
46+
template <class _Iter, class _Sent>
47+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __assume_valid_range(_Iter&& __first, _Sent&& __last) {
48+
#if __has_builtin(__builtin_assume_dereferenceable) && !defined(_LIBCPP_CXX03_LANG)
49+
if constexpr (__libcpp_is_contiguous_iterator<__remove_cvref_t<_Iter>>::value &&
50+
is_same<__remove_cvref_t<_Iter>, __remove_cvref_t<_Sent>>::value) {
51+
_LIBCPP_ASSERT_INTERNAL(std::__is_valid_range(std::__to_address(__first), std::__to_address(__last)),
52+
"Valid range assumption does not hold");
53+
if (!__libcpp_is_constant_evaluated()) {
54+
using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
55+
__builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
56+
(void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
57+
(void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
58+
}
59+
}
60+
#endif
61+
}
62+
63+
_LIBCPP_END_NAMESPACE_STD
64+
65+
#endif // _LIBCPP___MEMORY_VALID_RANGE_H

libcxx/include/__utility/is_pointer_in_range.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
#include <__algorithm/comp.h>
1313
#include <__assert>
1414
#include <__config>
15+
#include <__memory/valid_range.h>
1516
#include <__type_traits/enable_if.h>
1617
#include <__type_traits/integral_constant.h>
1718
#include <__type_traits/is_constant_evaluated.h>
1819
#include <__type_traits/void_t.h>
1920
#include <__utility/declval.h>
20-
#include <__utility/is_valid_range.h>
2121

2222
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2323
# pragma GCC system_header

libcxx/include/__utility/is_valid_range.h

Lines changed: 0 additions & 37 deletions
This file was deleted.

libcxx/include/module.modulemap.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,7 @@ module std [system] {
16831683
}
16841684
module uses_allocator { header "__memory/uses_allocator.h" }
16851685
module uses_allocator_construction { header "__memory/uses_allocator_construction.h" }
1686+
module valid_range { header "__memory/valid_range.h" }
16861687

16871688
header "memory"
16881689
export *
@@ -2161,7 +2162,6 @@ module std [system] {
21612162
}
21622163
module integer_sequence { header "__utility/integer_sequence.h" }
21632164
module is_pointer_in_range { header "__utility/is_pointer_in_range.h" }
2164-
module is_valid_range { header "__utility/is_valid_range.h" }
21652165
module lazy_synth_three_way_comparator { header "__utility/lazy_synth_three_way_comparator.h" }
21662166
module move { header "__utility/move.h" }
21672167
module no_destroy { header "__utility/no_destroy.h" }

libcxx/include/streambuf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ protected:
117117
# include <__assert>
118118
# include <__fwd/streambuf.h>
119119
# include <__locale>
120+
# include <__memory/valid_range.h>
120121
# include <__type_traits/is_same.h>
121-
# include <__utility/is_valid_range.h>
122122
# include <__utility/scope_guard.h>
123123
# include <climits>
124124
# include <ios>

libcxx/test/benchmarks/algorithms/nonmodifying/find.bench.cpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,18 @@
2121
int main(int argc, char** argv) {
2222
auto std_find = [](auto first, auto last, auto const& value) { return std::find(first, last, value); };
2323
auto std_find_if = [](auto first, auto last, auto const& value) {
24-
return std::find_if(first, last, [&](auto element) {
25-
benchmark::DoNotOptimize(element);
26-
return element == value;
27-
});
24+
return std::find_if(first, last, [&](auto element) { return element == value; });
2825
};
2926
auto std_find_if_not = [](auto first, auto last, auto const& value) {
30-
return std::find_if_not(first, last, [&](auto element) {
31-
benchmark::DoNotOptimize(element);
32-
return element != value;
33-
});
27+
return std::find_if_not(first, last, [&](auto element) { return element != value; });
3428
};
3529

3630
auto ranges_find = [](auto first, auto last, auto const& value) { return std::ranges::find(first, last, value); };
3731
auto ranges_find_if = [](auto first, auto last, auto const& value) {
38-
return std::ranges::find_if(first, last, [&](auto element) {
39-
benchmark::DoNotOptimize(element);
40-
return element == value;
41-
});
32+
return std::ranges::find_if(first, last, [&](auto element) { return element == value; });
4233
};
4334
auto ranges_find_if_not = [](auto first, auto last, auto const& value) {
44-
return std::ranges::find_if_not(first, last, [&](auto element) {
45-
benchmark::DoNotOptimize(element);
46-
return element != value;
47-
});
35+
return std::ranges::find_if_not(first, last, [&](auto element) { return element != value; });
4836
};
4937

5038
auto register_benchmarks = [&](auto bm, std::string comment) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <benchmark/benchmark.h>
2+
3+
template <class Iter, class ValueT>
4+
Iter my_find(Iter first, Iter last, const ValueT& i) {
5+
for (; first != last; ++first) {
6+
if (*first == i)
7+
break;
8+
}
9+
return first;
10+
}
11+
12+
static auto bm_find_if_no_vectorization(benchmark::State& state) {
13+
std::size_t const size = 8192;
14+
std::vector<short> c(size, 0);
15+
16+
for ([[maybe_unused]] auto _ : state) {
17+
benchmark::DoNotOptimize(c);
18+
std::vector<short>::iterator result;
19+
result = my_find(c.begin(), c.end(), 1);
20+
benchmark::DoNotOptimize(result);
21+
}
22+
}
23+
BENCHMARK(bm_find_if_no_vectorization);
24+
25+
static auto bm_find_if_autovectorization(benchmark::State& state) {
26+
std::size_t const size = 8192;
27+
std::vector<short> c(size, 0);
28+
29+
for ([[maybe_unused]] auto _ : state) {
30+
benchmark::DoNotOptimize(c);
31+
std::vector<short>::iterator result;
32+
result = find_if(c.begin(), c.end(), [](short i) { return i == 1; });
33+
benchmark::DoNotOptimize(result);
34+
}
35+
}
36+
BENCHMARK(bm_find_if_autovectorization);
37+
38+
static auto bm_find_manual_vectorization(benchmark::State& state) {
39+
std::size_t const size = 8192;
40+
std::vector<short> c(size, 0);
41+
42+
for ([[maybe_unused]] auto _ : state) {
43+
benchmark::DoNotOptimize(c);
44+
std::vector<short>::iterator result;
45+
result = find(c.begin(), c.end(), 1);
46+
benchmark::DoNotOptimize(result);
47+
}
48+
}
49+
BENCHMARK(bm_find_manual_vectorization);
50+
51+
BENCHMARK_MAIN();

libcxx/test/libcxx/utilities/is_valid_range.pass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#include <__utility/is_valid_range.h>
9+
#include <__memory/valid_range.h>
1010
#include <cassert>
1111

1212
#include "test_macros.h"

0 commit comments

Comments
 (0)