[libc++] Optimize __tree::find and __tree::__erase_unique (#152370)

This patch changes `__tree::find` to return when it has found any equal
element instead of the lower bound of the equal elements. For `map` and
`set` there is no observable difference, since the keys are unique.
However for their `multi` versions this can mean a change in behaviour
since it's not longer guaranteed that `find` will return the first
element.

```
------------------------------------------------------------------------------------------
Benchmark                                                                  old         new
------------------------------------------------------------------------------------------
std::map<int, int>::erase(key) (existent)/0                           24.4 ns      24.9 ns
std::map<int, int>::erase(key) (existent)/32                          39.8 ns      32.1 ns
std::map<int, int>::erase(key) (existent)/1024                        83.8 ns      52.5 ns
std::map<int, int>::erase(key) (existent)/8192                        91.4 ns      66.4 ns
std::map<int, int>::erase(key) (non-existent)/0                      0.511 ns     0.328 ns
std::map<int, int>::erase(key) (non-existent)/32                      9.12 ns      5.62 ns
std::map<int, int>::erase(key) (non-existent)/1024                    26.6 ns      11.3 ns
std::map<int, int>::erase(key) (non-existent)/8192                    37.0 ns      16.9 ns
std::map<int, int>::find(key) (existent)/0                           0.007 ns     0.007 ns
std::map<int, int>::find(key) (existent)/32                           6.02 ns      4.32 ns
std::map<int, int>::find(key) (existent)/1024                         13.6 ns      8.35 ns
std::map<int, int>::find(key) (existent)/8192                         30.3 ns      12.8 ns
std::map<int, int>::find(key) (non-existent)/0                       0.299 ns     0.545 ns
std::map<int, int>::find(key) (non-existent)/32                       8.78 ns      4.60 ns
std::map<int, int>::find(key) (non-existent)/1024                     26.1 ns      21.8 ns
std::map<int, int>::find(key) (non-existent)/8192                     36.2 ns      27.9 ns
std::map<std::string, int>::erase(key) (existent)/0                   74.1 ns      76.7 ns
std::map<std::string, int>::erase(key) (existent)/32                   161 ns       114 ns
std::map<std::string, int>::erase(key) (existent)/1024                 196 ns       126 ns
std::map<std::string, int>::erase(key) (existent)/8192                 207 ns       160 ns
std::map<std::string, int>::erase(key) (non-existent)/0              0.754 ns     0.328 ns
std::map<std::string, int>::erase(key) (non-existent)/32              47.3 ns      40.7 ns
std::map<std::string, int>::erase(key) (non-existent)/1024             122 ns      96.1 ns
std::map<std::string, int>::erase(key) (non-existent)/8192             168 ns       123 ns
std::map<std::string, int>::find(key) (existent)/0                   0.059 ns     0.058 ns
std::map<std::string, int>::find(key) (existent)/32                   54.3 ns      34.6 ns
std::map<std::string, int>::find(key) (existent)/1024                  125 ns      64.5 ns
std::map<std::string, int>::find(key) (existent)/8192                  159 ns      79.2 ns
std::map<std::string, int>::find(key) (non-existent)/0               0.311 ns     0.299 ns
std::map<std::string, int>::find(key) (non-existent)/32               44.0 ns      42.7 ns
std::map<std::string, int>::find(key) (non-existent)/1024              120 ns      92.6 ns
std::map<std::string, int>::find(key) (non-existent)/8192              189 ns       124 ns
std::set<int>::erase(key) (existent)/0                                25.1 ns      25.1 ns
std::set<int>::erase(key) (existent)/32                               42.1 ns      33.1 ns
std::set<int>::erase(key) (existent)/1024                             73.8 ns      55.5 ns
std::set<int>::erase(key) (existent)/8192                              101 ns      68.8 ns
std::set<int>::erase(key) (non-existent)/0                           0.511 ns     0.328 ns
std::set<int>::erase(key) (non-existent)/32                           9.60 ns      4.67 ns
std::set<int>::erase(key) (non-existent)/1024                         26.5 ns      11.2 ns
std::set<int>::erase(key) (non-existent)/8192                         46.2 ns      16.8 ns
std::set<int>::find(key) (existent)/0                                0.008 ns     0.007 ns
std::set<int>::find(key) (existent)/32                                5.87 ns      4.51 ns
std::set<int>::find(key) (existent)/1024                              14.3 ns      8.69 ns
std::set<int>::find(key) (existent)/8192                              30.2 ns      12.8 ns
std::set<int>::find(key) (non-existent)/0                            0.531 ns     0.530 ns
std::set<int>::find(key) (non-existent)/32                            8.77 ns      4.64 ns
std::set<int>::find(key) (non-existent)/1024                          26.1 ns      21.7 ns
std::set<int>::find(key) (non-existent)/8192                          36.3 ns      27.8 ns
std::set<std::string>::erase(key) (existent)/0                        93.2 ns      70.2 ns
std::set<std::string>::erase(key) (existent)/32                        164 ns       116 ns
std::set<std::string>::erase(key) (existent)/1024                      161 ns       136 ns
std::set<std::string>::erase(key) (existent)/8192                      231 ns       140 ns
std::set<std::string>::erase(key) (non-existent)/0                   0.532 ns     0.326 ns
std::set<std::string>::erase(key) (non-existent)/32                   43.4 ns      40.1 ns
std::set<std::string>::erase(key) (non-existent)/1024                  122 ns      99.5 ns
std::set<std::string>::erase(key) (non-existent)/8192                  168 ns       125 ns
std::set<std::string>::find(key) (existent)/0                        0.059 ns     0.059 ns
std::set<std::string>::find(key) (existent)/32                        53.1 ns      35.5 ns
std::set<std::string>::find(key) (existent)/1024                       124 ns      61.2 ns
std::set<std::string>::find(key) (existent)/8192                       154 ns      73.9 ns
std::set<std::string>::find(key) (non-existent)/0                    0.532 ns     0.301 ns
std::set<std::string>::find(key) (non-existent)/32                    44.4 ns      39.5 ns
std::set<std::string>::find(key) (non-existent)/1024                   120 ns      95.5 ns
std::set<std::string>::find(key) (non-existent)/8192                   193 ns       119 ns
std::multimap<int, int>::erase(key) (existent)/0                       26.5 ns     26.6 ns
std::multimap<int, int>::erase(key) (existent)/32                      33.5 ns     32.9 ns
std::multimap<int, int>::erase(key) (existent)/1024                    55.5 ns     58.0 ns
std::multimap<int, int>::erase(key) (existent)/8192                    67.4 ns     70.0 ns
std::multimap<int, int>::erase(key) (non-existent)/0                  0.523 ns    0.532 ns
std::multimap<int, int>::erase(key) (non-existent)/32                  5.08 ns     5.09 ns
std::multimap<int, int>::erase(key) (non-existent)/1024                13.0 ns     12.9 ns
std::multimap<int, int>::erase(key) (non-existent)/8192                19.6 ns     19.8 ns
std::multimap<int, int>::find(key) (existent)/0                       0.015 ns    0.037 ns
std::multimap<int, int>::find(key) (existent)/32                       7.07 ns     3.85 ns
std::multimap<int, int>::find(key) (existent)/1024                     22.0 ns     7.44 ns
std::multimap<int, int>::find(key) (existent)/8192                     37.6 ns     12.0 ns
std::multimap<int, int>::find(key) (non-existent)/0                   0.297 ns    0.305 ns
std::multimap<int, int>::find(key) (non-existent)/32                   8.79 ns     4.59 ns
std::multimap<int, int>::find(key) (non-existent)/1024                 26.0 ns     11.2 ns
std::multimap<int, int>::find(key) (non-existent)/8192                 36.4 ns     16.8 ns
std::multimap<std::string, int>::erase(key) (existent)/0               93.4 ns     84.5 ns
std::multimap<std::string, int>::erase(key) (existent)/32               101 ns      101 ns
std::multimap<std::string, int>::erase(key) (existent)/1024             118 ns      126 ns
std::multimap<std::string, int>::erase(key) (existent)/8192             108 ns      124 ns
std::multimap<std::string, int>::erase(key) (non-existent)/0           2.39 ns     2.43 ns
std::multimap<std::string, int>::erase(key) (non-existent)/32          44.4 ns     49.7 ns
std::multimap<std::string, int>::erase(key) (non-existent)/1024         108 ns      103 ns
std::multimap<std::string, int>::erase(key) (non-existent)/8192         140 ns      125 ns
std::multimap<std::string, int>::find(key) (existent)/0               0.059 ns    0.058 ns
std::multimap<std::string, int>::find(key) (existent)/32               52.3 ns     32.6 ns
std::multimap<std::string, int>::find(key) (existent)/1024              122 ns     58.9 ns
std::multimap<std::string, int>::find(key) (existent)/8192              160 ns     72.7 ns
std::multimap<std::string, int>::find(key) (non-existent)/0           0.524 ns    0.494 ns
std::multimap<std::string, int>::find(key) (non-existent)/32           43.8 ns     38.9 ns
std::multimap<std::string, int>::find(key) (non-existent)/1024          123 ns     90.8 ns
std::multimap<std::string, int>::find(key) (non-existent)/8192          190 ns      126 ns
std::multiset<int>::erase(key) (existent)/0                            27.1 ns     26.8 ns
std::multiset<int>::erase(key) (existent)/32                           33.3 ns     34.1 ns
std::multiset<int>::erase(key) (existent)/1024                         58.5 ns     58.8 ns
std::multiset<int>::erase(key) (existent)/8192                         66.7 ns     64.1 ns
std::multiset<int>::erase(key) (non-existent)/0                       0.318 ns    0.325 ns
std::multiset<int>::erase(key) (non-existent)/32                       5.15 ns     5.25 ns
std::multiset<int>::erase(key) (non-existent)/1024                     12.9 ns     12.7 ns
std::multiset<int>::erase(key) (non-existent)/8192                     20.3 ns     20.3 ns
std::multiset<int>::find(key) (existent)/0                            0.043 ns    0.015 ns
std::multiset<int>::find(key) (existent)/32                            6.94 ns     4.22 ns
std::multiset<int>::find(key) (existent)/1024                          21.4 ns     8.23 ns
std::multiset<int>::find(key) (existent)/8192                          37.4 ns     12.6 ns
std::multiset<int>::find(key) (non-existent)/0                        0.515 ns    0.300 ns
std::multiset<int>::find(key) (non-existent)/32                        8.52 ns     4.62 ns
std::multiset<int>::find(key) (non-existent)/1024                      25.5 ns     11.3 ns
std::multiset<int>::find(key) (non-existent)/8192                      36.5 ns     27.0 ns
std::multiset<std::string>::erase(key) (existent)/0                    81.9 ns     77.5 ns
std::multiset<std::string>::erase(key) (existent)/32                    113 ns      129 ns
std::multiset<std::string>::erase(key) (existent)/1024                  132 ns      148 ns
std::multiset<std::string>::erase(key) (existent)/8192                  114 ns      165 ns
std::multiset<std::string>::erase(key) (non-existent)/0                2.33 ns     2.32 ns
std::multiset<std::string>::erase(key) (non-existent)/32               44.4 ns     42.0 ns
std::multiset<std::string>::erase(key) (non-existent)/1024             97.3 ns     95.1 ns
std::multiset<std::string>::erase(key) (non-existent)/8192              132 ns      123 ns
std::multiset<std::string>::find(key) (existent)/0                    0.058 ns    0.059 ns
std::multiset<std::string>::find(key) (existent)/32                    48.3 ns     34.4 ns
std::multiset<std::string>::find(key) (existent)/1024                   121 ns     61.9 ns
std::multiset<std::string>::find(key) (existent)/8192                   155 ns     77.7 ns
std::multiset<std::string>::find(key) (non-existent)/0                0.524 ns    0.306 ns
std::multiset<std::string>::find(key) (non-existent)/32                44.1 ns     40.4 ns
std::multiset<std::string>::find(key) (non-existent)/1024               121 ns     96.3 ns
std::multiset<std::string>::find(key) (non-existent)/8192               193 ns      121 ns
```
This commit is contained in:
Nikolas Klauser 2025-08-15 08:59:39 +02:00 committed by GitHub
parent cbbf303ff5
commit d9bc548fac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 42 deletions

View File

@ -47,6 +47,8 @@ Improvements and New Features
- The performance of ``map::operator=(const map&)`` has been improved by up to 11x - The performance of ``map::operator=(const map&)`` has been improved by up to 11x
- The performance of ``unordered_set::unordered_set(const unordered_set&)`` has been improved by up to 3.3x. - The performance of ``unordered_set::unordered_set(const unordered_set&)`` has been improved by up to 3.3x.
- The performance of ``unordered_set::operator=(const unordered_set&)`` has been improved by up to 5x. - The performance of ``unordered_set::operator=(const unordered_set&)`` has been improved by up to 5x.
- The performance of ``map::erase`` and ``set::erase`` has been improved by up to 2x
- The performance of ``find(key)`` in ``map``, ``set``, ``multimap`` and ``multiset`` has been improved by up to 2.3x
Deprecations and Removals Deprecations and Removals
------------------------- -------------------------

View File

@ -1038,9 +1038,22 @@ public:
__insert_node_at(__end_node_pointer __parent, __node_base_pointer& __child, __node_base_pointer __new_node) _NOEXCEPT; __insert_node_at(__end_node_pointer __parent, __node_base_pointer& __child, __node_base_pointer __new_node) _NOEXCEPT;
template <class _Key> template <class _Key>
_LIBCPP_HIDE_FROM_ABI iterator find(const _Key& __v); _LIBCPP_HIDE_FROM_ABI iterator find(const _Key& __key) {
__end_node_pointer __parent;
__node_base_pointer __match = __find_equal(__parent, __key);
if (__match == nullptr)
return end();
return iterator(static_cast<__node_pointer>(__match));
}
template <class _Key> template <class _Key>
_LIBCPP_HIDE_FROM_ABI const_iterator find(const _Key& __v) const; _LIBCPP_HIDE_FROM_ABI const_iterator find(const _Key& __key) const {
__end_node_pointer __parent;
__node_base_pointer __match = __find_equal(__parent, __key);
if (__match == nullptr)
return end();
return const_iterator(static_cast<__node_pointer>(__match));
}
template <class _Key> template <class _Key>
_LIBCPP_HIDE_FROM_ABI size_type __count_unique(const _Key& __k) const; _LIBCPP_HIDE_FROM_ABI size_type __count_unique(const _Key& __k) const;
@ -2060,25 +2073,6 @@ __tree<_Tp, _Compare, _Allocator>::__erase_multi(const _Key& __k) {
return __r; return __r;
} }
template <class _Tp, class _Compare, class _Allocator>
template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::iterator __tree<_Tp, _Compare, _Allocator>::find(const _Key& __v) {
iterator __p = __lower_bound(__v, __root(), __end_node());
if (__p != end() && !value_comp()(__v, *__p))
return __p;
return end();
}
template <class _Tp, class _Compare, class _Allocator>
template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::const_iterator
__tree<_Tp, _Compare, _Allocator>::find(const _Key& __v) const {
const_iterator __p = __lower_bound(__v, __root(), __end_node());
if (__p != end() && !value_comp()(__v, *__p))
return __p;
return end();
}
template <class _Tp, class _Compare, class _Allocator> template <class _Tp, class _Compare, class _Allocator>
template <class _Key> template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::size_type typename __tree<_Tp, _Compare, _Allocator>::size_type

View File

@ -21,6 +21,15 @@
#include "private_constructor.h" #include "private_constructor.h"
#include "is_transparent.h" #include "is_transparent.h"
template <class Iter>
bool iter_in_range(Iter first, Iter last, Iter to_find) {
for (; first != last; ++first) {
if (first == to_find)
return true;
}
return false;
}
int main(int, char**) { int main(int, char**) {
typedef std::pair<const int, double> V; typedef std::pair<const int, double> V;
{ {
@ -30,15 +39,15 @@ int main(int, char**) {
V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)}; V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)};
M m(ar, ar + sizeof(ar) / sizeof(ar[0])); M m(ar, ar + sizeof(ar) / sizeof(ar[0]));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
} }
@ -47,15 +56,15 @@ int main(int, char**) {
V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)}; V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)};
const M m(ar, ar + sizeof(ar) / sizeof(ar[0])); const M m(ar, ar + sizeof(ar) / sizeof(ar[0]));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
} }
@ -68,15 +77,15 @@ int main(int, char**) {
V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)}; V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)};
M m(ar, ar + sizeof(ar) / sizeof(ar[0])); M m(ar, ar + sizeof(ar) / sizeof(ar[0]));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
} }
@ -85,15 +94,15 @@ int main(int, char**) {
V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)}; V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)};
const M m(ar, ar + sizeof(ar) / sizeof(ar[0])); const M m(ar, ar + sizeof(ar) / sizeof(ar[0]));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
} }
@ -107,28 +116,28 @@ int main(int, char**) {
V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)}; V ar[] = {V(5, 1), V(5, 2), V(5, 3), V(7, 1), V(7, 2), V(7, 3), V(9, 1), V(9, 2), V(9, 3)};
M m(ar, ar + sizeof(ar) / sizeof(ar[0])); M m(ar, ar + sizeof(ar) / sizeof(ar[0]));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
r = m.find(C2Int(5)); r = m.find(C2Int(5));
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(C2Int(6)); r = m.find(C2Int(6));
assert(r == m.end()); assert(r == m.end());
r = m.find(C2Int(7)); r = m.find(C2Int(7));
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(C2Int(8)); r = m.find(C2Int(8));
assert(r == m.end()); assert(r == m.end());
r = m.find(C2Int(9)); r = m.find(C2Int(9));
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(C2Int(10)); r = m.find(C2Int(10));
assert(r == m.end()); assert(r == m.end());
} }
@ -150,15 +159,15 @@ int main(int, char**) {
m.insert(std::make_pair<PC, double>(PC::make(9), 3)); m.insert(std::make_pair<PC, double>(PC::make(9), 3));
R r = m.find(5); R r = m.find(5);
assert(r == m.begin()); assert(iter_in_range(std::next(m.begin(), 0), std::next(m.begin(), 3), r));
r = m.find(6); r = m.find(6);
assert(r == m.end()); assert(r == m.end());
r = m.find(7); r = m.find(7);
assert(r == std::next(m.begin(), 3)); assert(iter_in_range(std::next(m.begin(), 3), std::next(m.begin(), 6), r));
r = m.find(8); r = m.find(8);
assert(r == m.end()); assert(r == m.end());
r = m.find(9); r = m.find(9);
assert(r == std::next(m.begin(), 6)); assert(iter_in_range(std::next(m.begin(), 6), std::next(m.begin(), 9), r));
r = m.find(10); r = m.find(10);
assert(r == m.end()); assert(r == m.end());
} }