Skip to content

Commit 1c7f140

Browse files
authored
[libc++] Optimize multi{map,set}::insert(InputIterator, InputIterator) (llvm#152691)
1 parent 6896725 commit 1c7f140

File tree

6 files changed

+394
-127
lines changed

6 files changed

+394
-127
lines changed

libcxx/docs/ReleaseNotes/22.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Improvements and New Features
5151
- The performance of ``map::erase`` and ``set::erase`` has been improved by up to 2x
5252
- The performance of ``find(key)`` in ``map``, ``set``, ``multimap`` and ``multiset`` has been improved by up to 2.3x
5353
- Some reallocations are now avoided in `std::filesystem::path::lexically_relative`, resulting in a performance improvement of up to 1.7x.
54+
- The performance of the ``(iterator, iterator)`` constructors of ``multimap`` and ``multiset``
55+
has been improved by up to 3x
56+
- The performance of ``insert(iterator, iterator)`` of ``multimap`` and ``multiset`` has been improved by up to 2.5x
5457

5558
Deprecations and Removals
5659
-------------------------

libcxx/include/__tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,35 @@ public:
10051005
__emplace_hint_multi(__p, std::move(__value));
10061006
}
10071007

1008+
template <class _InIter, class _Sent>
1009+
_LIBCPP_HIDE_FROM_ABI void __insert_range_multi(_InIter __first, _Sent __last) {
1010+
if (__first == __last)
1011+
return;
1012+
1013+
if (__root() == nullptr) { // Make sure we always have a root node
1014+
__insert_node_at(
1015+
__end_node(), __end_node()->__left_, static_cast<__node_base_pointer>(__construct_node(*__first).release()));
1016+
++__first;
1017+
}
1018+
1019+
auto __max_node = static_cast<__node_pointer>(std::__tree_max(static_cast<__node_base_pointer>(__root())));
1020+
1021+
for (; __first != __last; ++__first) {
1022+
__node_holder __nd = __construct_node(*__first);
1023+
// Always check the max node first. This optimizes for sorted ranges inserted at the end.
1024+
if (!value_comp()(__nd->__get_value(), __max_node->__get_value())) { // __node >= __max_val
1025+
__insert_node_at(static_cast<__end_node_pointer>(__max_node),
1026+
__max_node->__right_,
1027+
static_cast<__node_base_pointer>(__nd.get()));
1028+
__max_node = __nd.release();
1029+
} else {
1030+
__end_node_pointer __parent;
1031+
__node_base_pointer& __child = __find_leaf_high(__parent, __nd->__get_value());
1032+
__insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__nd.release()));
1033+
}
1034+
}
1035+
}
1036+
10081037
_LIBCPP_HIDE_FROM_ABI pair<iterator, bool> __node_assign_unique(const value_type& __v, __node_pointer __dest);
10091038

10101039
_LIBCPP_HIDE_FROM_ABI iterator __node_insert_multi(__node_pointer __nd);

libcxx/include/map

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ erase_if(multimap<Key, T, Compare, Allocator>& c, Predicate pred); // C++20
593593
# include <__memory/unique_ptr.h>
594594
# include <__memory_resource/polymorphic_allocator.h>
595595
# include <__node_handle>
596+
# include <__ranges/access.h>
596597
# include <__ranges/concepts.h>
597598
# include <__ranges/container_compatible_range.h>
598599
# include <__ranges/from_range.h>
@@ -1749,17 +1750,13 @@ public:
17491750

17501751
template <class _InputIterator>
17511752
_LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __f, _InputIterator __l) {
1752-
for (const_iterator __e = cend(); __f != __l; ++__f)
1753-
__tree_.__emplace_hint_multi(__e.__i_, *__f);
1753+
__tree_.__insert_range_multi(__f, __l);
17541754
}
17551755

17561756
# if _LIBCPP_STD_VER >= 23
17571757
template <_ContainerCompatibleRange<value_type> _Range>
17581758
_LIBCPP_HIDE_FROM_ABI void insert_range(_Range&& __range) {
1759-
const_iterator __end = cend();
1760-
for (auto&& __element : __range) {
1761-
__tree_.__emplace_hint_multi(__end.__i_, std::forward<decltype(__element)>(__element));
1762-
}
1759+
__tree_.__insert_range_multi(ranges::begin(__range), ranges::end(__range));
17631760
}
17641761
# endif
17651762

libcxx/include/set

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ erase_if(multiset<Key, Compare, Allocator>& c, Predicate pred); // C++20
530530
# include <__memory/allocator_traits.h>
531531
# include <__memory_resource/polymorphic_allocator.h>
532532
# include <__node_handle>
533+
# include <__ranges/access.h>
533534
# include <__ranges/concepts.h>
534535
# include <__ranges/container_compatible_range.h>
535536
# include <__ranges/from_range.h>
@@ -1199,18 +1200,14 @@ public:
11991200
}
12001201

12011202
template <class _InputIterator>
1202-
_LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __f, _InputIterator __l) {
1203-
for (const_iterator __e = cend(); __f != __l; ++__f)
1204-
__tree_.__emplace_hint_multi(__e, *__f);
1203+
_LIBCPP_HIDE_FROM_ABI void insert(_InputIterator __first, _InputIterator __last) {
1204+
__tree_.__insert_range_multi(__first, __last);
12051205
}
12061206

12071207
# if _LIBCPP_STD_VER >= 23
12081208
template <_ContainerCompatibleRange<value_type> _Range>
12091209
_LIBCPP_HIDE_FROM_ABI void insert_range(_Range&& __range) {
1210-
const_iterator __end = cend();
1211-
for (auto&& __element : __range) {
1212-
__tree_.__emplace_hint_multi(__end, std::forward<decltype(__element)>(__element));
1213-
}
1210+
__tree_.__insert_range_multi(ranges::begin(__range), ranges::end(__range));
12141211
}
12151212
# endif
12161213

libcxx/test/std/containers/associative/multimap/multimap.modifiers/insert_iter_iter.pass.cpp

Lines changed: 183 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,88 +13,195 @@
1313
// template <class InputIterator>
1414
// void insert(InputIterator first, InputIterator last);
1515

16-
#include <map>
16+
#include <array>
1717
#include <cassert>
18+
#include <map>
1819

19-
#include "test_macros.h"
20-
#include "test_iterators.h"
2120
#include "min_allocator.h"
21+
#include "test_iterators.h"
22+
#include "test_macros.h"
2223

23-
int main(int, char**) {
24-
{
25-
typedef std::multimap<int, double> M;
26-
typedef std::pair<int, double> P;
27-
P ar[] = {
28-
P(1, 1),
29-
P(1, 1.5),
30-
P(1, 2),
31-
P(2, 1),
32-
P(2, 1.5),
33-
P(2, 2),
34-
P(3, 1),
35-
P(3, 1.5),
36-
P(3, 2),
37-
};
38-
M m;
39-
m.insert(cpp17_input_iterator<P*>(ar), cpp17_input_iterator<P*>(ar + sizeof(ar) / sizeof(ar[0])));
40-
assert(m.size() == 9);
41-
assert(m.begin()->first == 1);
42-
assert(m.begin()->second == 1);
43-
assert(std::next(m.begin())->first == 1);
44-
assert(std::next(m.begin())->second == 1.5);
45-
assert(std::next(m.begin(), 2)->first == 1);
46-
assert(std::next(m.begin(), 2)->second == 2);
47-
assert(std::next(m.begin(), 3)->first == 2);
48-
assert(std::next(m.begin(), 3)->second == 1);
49-
assert(std::next(m.begin(), 4)->first == 2);
50-
assert(std::next(m.begin(), 4)->second == 1.5);
51-
assert(std::next(m.begin(), 5)->first == 2);
52-
assert(std::next(m.begin(), 5)->second == 2);
53-
assert(std::next(m.begin(), 6)->first == 3);
54-
assert(std::next(m.begin(), 6)->second == 1);
55-
assert(std::next(m.begin(), 7)->first == 3);
56-
assert(std::next(m.begin(), 7)->second == 1.5);
57-
assert(std::next(m.begin(), 8)->first == 3);
58-
assert(std::next(m.begin(), 8)->second == 2);
24+
template <class Iter, class Alloc>
25+
void test_alloc() {
26+
{ // Check that an empty range works correctly
27+
{ // Without elements in the container
28+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
29+
30+
std::array<std::pair<const int, int>, 0> arr;
31+
32+
Map map;
33+
map.insert(Iter(arr.data()), Iter(arr.data() + arr.size()));
34+
assert(map.size() == 0);
35+
assert(map.begin() == map.end());
36+
}
37+
{ // With 1 element in the container
38+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
39+
using Pair = std::pair<const int, int>;
40+
41+
std::array<Pair, 0> arr;
42+
43+
Map map;
44+
map.insert(Pair(0, 0));
45+
map.insert(Iter(arr.data()), Iter(arr.data() + arr.size()));
46+
assert(map.size() == 1);
47+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
48+
assert(std::next(map.begin(), 1) == map.end());
49+
}
50+
{ // With multiple elements in the container
51+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
52+
using Pair = std::pair<const int, int>;
53+
54+
std::array<Pair, 0> arr;
55+
56+
Map map;
57+
map.insert(Pair(0, 0));
58+
map.insert(Pair(1, 1));
59+
map.insert(Pair(2, 2));
60+
map.insert(Iter(arr.data()), Iter(arr.data() + arr.size()));
61+
assert(map.size() == 3);
62+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
63+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
64+
assert(*std::next(map.begin(), 2) == Pair(2, 2));
65+
assert(std::next(map.begin(), 3) == map.end());
66+
}
5967
}
60-
#if TEST_STD_VER >= 11
61-
{
62-
typedef std::multimap<int, double, std::less<int>, min_allocator<std::pair<const int, double>>> M;
63-
typedef std::pair<int, double> P;
64-
P ar[] = {
65-
P(1, 1),
66-
P(1, 1.5),
67-
P(1, 2),
68-
P(2, 1),
69-
P(2, 1.5),
70-
P(2, 2),
71-
P(3, 1),
72-
P(3, 1.5),
73-
P(3, 2),
74-
};
75-
M m;
76-
m.insert(cpp17_input_iterator<P*>(ar), cpp17_input_iterator<P*>(ar + sizeof(ar) / sizeof(ar[0])));
77-
assert(m.size() == 9);
78-
assert(m.begin()->first == 1);
79-
assert(m.begin()->second == 1);
80-
assert(std::next(m.begin())->first == 1);
81-
assert(std::next(m.begin())->second == 1.5);
82-
assert(std::next(m.begin(), 2)->first == 1);
83-
assert(std::next(m.begin(), 2)->second == 2);
84-
assert(std::next(m.begin(), 3)->first == 2);
85-
assert(std::next(m.begin(), 3)->second == 1);
86-
assert(std::next(m.begin(), 4)->first == 2);
87-
assert(std::next(m.begin(), 4)->second == 1.5);
88-
assert(std::next(m.begin(), 5)->first == 2);
89-
assert(std::next(m.begin(), 5)->second == 2);
90-
assert(std::next(m.begin(), 6)->first == 3);
91-
assert(std::next(m.begin(), 6)->second == 1);
92-
assert(std::next(m.begin(), 7)->first == 3);
93-
assert(std::next(m.begin(), 7)->second == 1.5);
94-
assert(std::next(m.begin(), 8)->first == 3);
95-
assert(std::next(m.begin(), 8)->second == 2);
68+
{ // Check that 1 element is inserted correctly
69+
{ // Without elements in the container
70+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
71+
using Pair = std::pair<const int, int>;
72+
73+
Pair arr[] = {Pair(1, 1)};
74+
75+
Map map;
76+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
77+
assert(map.size() == 1);
78+
assert(*std::next(map.begin(), 0) == Pair(1, 1));
79+
assert(std::next(map.begin(), 1) == map.end());
80+
}
81+
{ // With 1 element in the container - a different key
82+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
83+
using Pair = std::pair<const int, int>;
84+
85+
Pair arr[] = {Pair(1, 1)};
86+
87+
Map map;
88+
map.insert(Pair(0, 0));
89+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
90+
assert(map.size() == 2);
91+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
92+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
93+
assert(std::next(map.begin(), 2) == map.end());
94+
}
95+
{ // With 1 element in the container - the same key
96+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
97+
using Pair = std::pair<const int, int>;
98+
99+
Pair arr[] = {Pair(1, 1)};
100+
101+
Map map;
102+
map.insert(Pair(1, 1));
103+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
104+
assert(map.size() == 2);
105+
assert(*std::next(map.begin(), 0) == Pair(1, 1));
106+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
107+
assert(std::next(map.begin(), 2) == map.end());
108+
}
109+
{ // With multiple elements in the container
110+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
111+
using Pair = std::pair<const int, int>;
112+
113+
Pair arr[] = {Pair(1, 1)};
114+
115+
Map map;
116+
map.insert(Pair(0, 0));
117+
map.insert(Pair(1, 1));
118+
map.insert(Pair(2, 2));
119+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
120+
assert(map.size() == 4);
121+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
122+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
123+
assert(*std::next(map.begin(), 2) == Pair(1, 1));
124+
assert(*std::next(map.begin(), 3) == Pair(2, 2));
125+
assert(std::next(map.begin(), 4) == map.end());
126+
}
96127
}
97-
#endif
128+
{ // Check that multiple elements are inserted correctly
129+
{ // Without elements in the container
130+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
131+
using Pair = std::pair<const int, int>;
132+
133+
Pair arr[] = {Pair(1, 1), Pair(1, 1), Pair(3, 3)};
134+
135+
Map map;
136+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
137+
assert(map.size() == 3);
138+
assert(*std::next(map.begin(), 0) == Pair(1, 1));
139+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
140+
assert(*std::next(map.begin(), 2) == Pair(3, 3));
141+
assert(std::next(map.begin(), 3) == map.end());
142+
}
143+
{ // With 1 element in the container - a different key
144+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
145+
using Pair = std::pair<const int, int>;
146+
147+
Pair arr[] = {Pair(1, 1), Pair(1, 1), Pair(3, 3)};
148+
149+
Map map;
150+
map.insert(Pair(0, 0));
151+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
152+
assert(map.size() == 4);
153+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
154+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
155+
assert(*std::next(map.begin(), 2) == Pair(1, 1));
156+
assert(*std::next(map.begin(), 3) == Pair(3, 3));
157+
assert(std::next(map.begin(), 4) == map.end());
158+
}
159+
{ // With 1 element in the container - the same key
160+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
161+
using Pair = std::pair<const int, int>;
162+
163+
Pair arr[] = {Pair(1, 1), Pair(2, 2), Pair(3, 3)};
164+
165+
Map map;
166+
map.insert(Pair(1, 1));
167+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
168+
assert(map.size() == 4);
169+
assert(*std::next(map.begin(), 0) == Pair(1, 1));
170+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
171+
assert(*std::next(map.begin(), 2) == Pair(2, 2));
172+
assert(*std::next(map.begin(), 3) == Pair(3, 3));
173+
assert(std::next(map.begin(), 4) == map.end());
174+
}
175+
{ // With multiple elements in the container
176+
using Map = std::multimap<int, int, std::less<int>, Alloc>;
177+
using Pair = std::pair<const int, int>;
178+
179+
Pair arr[] = {Pair(1, 1), Pair(3, 3), Pair(4, 4)};
180+
181+
Map map;
182+
map.insert(Pair(0, 0));
183+
map.insert(Pair(1, 1));
184+
map.insert(Pair(2, 2));
185+
map.insert(Iter(std::begin(arr)), Iter(std::end(arr)));
186+
assert(map.size() == 6);
187+
assert(*std::next(map.begin(), 0) == Pair(0, 0));
188+
assert(*std::next(map.begin(), 1) == Pair(1, 1));
189+
assert(*std::next(map.begin(), 2) == Pair(1, 1));
190+
assert(*std::next(map.begin(), 3) == Pair(2, 2));
191+
assert(*std::next(map.begin(), 4) == Pair(3, 3));
192+
assert(*std::next(map.begin(), 5) == Pair(4, 4));
193+
assert(std::next(map.begin(), 6) == map.end());
194+
}
195+
}
196+
}
197+
198+
void test() {
199+
test_alloc<cpp17_input_iterator<std::pair<const int, int>*>, std::allocator<std::pair<const int, int> > >();
200+
test_alloc<cpp17_input_iterator<std::pair<const int, int>*>, min_allocator<std::pair<const int, int> > >();
201+
}
202+
203+
int main(int, char**) {
204+
test();
98205

99206
return 0;
100207
}

0 commit comments

Comments
 (0)