Skip to content

Commit 97e4cc5

Browse files
committed
[libc++] Optimize copy construction and assignment of __tree
1 parent 740758a commit 97e4cc5

File tree

13 files changed

+926
-609
lines changed

13 files changed

+926
-609
lines changed

libcxx/docs/ReleaseNotes/22.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ Implemented Papers
4343
Improvements and New Features
4444
-----------------------------
4545

46+
- The performance of ``map::map(const map&)`` has been improved up to 2.3x
47+
- The performance of ``map::operator=(const map&)`` has been improved by up to 11x
48+
4649
Deprecations and Removals
4750
-------------------------
4851

libcxx/include/__tree

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,91 @@ private:
12131213
__node_pointer __cache_root_;
12141214
__node_pointer __cache_elem_;
12151215
};
1216+
1217+
class __tree_deleter {
1218+
__node_allocator& __alloc_;
1219+
1220+
public:
1221+
using pointer = __node_pointer;
1222+
1223+
_LIBCPP_HIDE_FROM_ABI __tree_deleter(__node_allocator& __alloc) : __alloc_(__alloc) {}
1224+
1225+
void operator()(__node_pointer __ptr) {
1226+
if (!__ptr)
1227+
return;
1228+
1229+
(*this)(static_cast<__node_pointer>(__ptr->__left_));
1230+
1231+
auto __right = __ptr->__right_;
1232+
1233+
__node_traits::destroy(__alloc_, std::addressof(__ptr->__value_));
1234+
__node_traits::deallocate(__alloc_, __ptr, 1);
1235+
1236+
(*this)(static_cast<__node_pointer>(__right));
1237+
}
1238+
};
1239+
1240+
// This copy construction will always produce a correct red-black-tree assuming the incoming tree is correct, since we
1241+
// copy the exact structure 1:1. Since this is for copy construction _only_ we know that we get a correct tree. If we
1242+
// didn't get a correct tree, the invariants of __tree are broken and we have a much bigger problem than an improperly
1243+
// balanced tree.
1244+
_LIBCPP_HIDE_FROM_ABI __node_pointer __copy_construct_tree(__node_pointer __src) {
1245+
if (!__src)
1246+
return nullptr;
1247+
1248+
__node_holder __new_node = __construct_node(__src->__value_);
1249+
1250+
unique_ptr<__node, __tree_deleter> __left(
1251+
__copy_construct_tree(static_cast<__node_pointer>(__src->__left_)), __node_alloc_);
1252+
__node_pointer __right = __copy_construct_tree(static_cast<__node_pointer>(__src->__right_));
1253+
1254+
__node_pointer __new_node_ptr = __new_node.release();
1255+
1256+
__new_node_ptr->__is_black_ = __src->__is_black_;
1257+
__new_node_ptr->__left_ = static_cast<__node_base_pointer>(__left.release());
1258+
__new_node_ptr->__right_ = static_cast<__node_base_pointer>(__right);
1259+
if (__new_node_ptr->__left_)
1260+
__new_node_ptr->__left_->__parent_ = static_cast<__end_node_pointer>(__new_node_ptr);
1261+
if (__new_node_ptr->__right_)
1262+
__new_node_ptr->__right_->__parent_ = static_cast<__end_node_pointer>(__new_node_ptr);
1263+
return __new_node_ptr;
1264+
}
1265+
1266+
// This copy assignment will always produce a correct red-black-tree assumign the incoming tree is correct, since our
1267+
// own tree is a red-black-tree and the incoming tree is a red-black-tree. The invariants of a red-black-tree are
1268+
// temporarily not met until all of the incoming red-black tree is copied.
1269+
_LIBCPP_HIDE_FROM_ABI __node_pointer __copy_assign_tree(__node_pointer __dest, __node_pointer __src) {
1270+
if (!__src) {
1271+
destroy(__dest);
1272+
return nullptr;
1273+
}
1274+
1275+
__assign_value(__dest->__value_, __src->__value_);
1276+
1277+
// If we already have a left node in the destination tree, reuse it and copy-assign recursively
1278+
if (__dest->__left_) {
1279+
__dest->__left_ = static_cast<__node_base_pointer>(__copy_assign_tree(
1280+
static_cast<__node_pointer>(__dest->__left_), static_cast<__node_pointer>(__src->__left_)));
1281+
1282+
// Otherwise, we must create new nodes; copy-construct from here on
1283+
} else if (__src->__left_) {
1284+
auto __new_left = __copy_construct_tree(static_cast<__node_pointer>(__src->__left_));
1285+
__dest->__left_ = static_cast<__node_base_pointer>(__new_left);
1286+
__new_left->__parent_ = static_cast<__end_node_pointer>(__dest);
1287+
}
1288+
1289+
// Identical to the left case above, just for the right nodes
1290+
if (__dest->__right_) {
1291+
__dest->__right_ = static_cast<__node_base_pointer>(__copy_assign_tree(
1292+
static_cast<__node_pointer>(__dest->__right_), static_cast<__node_pointer>(__src->__right_)));
1293+
} else if (__src->__right_) {
1294+
auto __new_right = __copy_construct_tree(static_cast<__node_pointer>(__src->__right_));
1295+
__dest->__right_ = static_cast<__node_base_pointer>(__new_right);
1296+
__new_right->__parent_ = static_cast<__end_node_pointer>(__dest);
1297+
}
1298+
1299+
return __dest;
1300+
}
12161301
};
12171302

12181303
template <class _Tp, class _Compare, class _Allocator>
@@ -1277,11 +1362,22 @@ __tree<_Tp, _Compare, _Allocator>::_DetachedTreeCache::__detach_next(__node_poin
12771362

12781363
template <class _Tp, class _Compare, class _Allocator>
12791364
__tree<_Tp, _Compare, _Allocator>& __tree<_Tp, _Compare, _Allocator>::operator=(const __tree& __t) {
1280-
if (this != std::addressof(__t)) {
1281-
value_comp() = __t.value_comp();
1282-
__copy_assign_alloc(__t);
1283-
__assign_multi(__t.begin(), __t.end());
1365+
if (this == std::addressof(__t))
1366+
return *this;
1367+
1368+
value_comp() = __t.value_comp();
1369+
__copy_assign_alloc(__t);
1370+
1371+
if (__size_ != 0) {
1372+
*__root_ptr() = static_cast<__node_base_pointer>(__copy_assign_tree(__root(), __t.__root()));
1373+
} else {
1374+
*__root_ptr() = static_cast<__node_base_pointer>(__copy_construct_tree(__t.__root()));
1375+
if (__root())
1376+
__root()->__parent_ = __end_node();
12841377
}
1378+
__begin_node_ = static_cast<__end_node_pointer>(std::__tree_min(static_cast<__node_base_pointer>(__end_node())));
1379+
__size_ = __t.size();
1380+
12851381
return *this;
12861382
}
12871383

@@ -1327,11 +1423,17 @@ void __tree<_Tp, _Compare, _Allocator>::__assign_multi(_InputIterator __first, _
13271423

13281424
template <class _Tp, class _Compare, class _Allocator>
13291425
__tree<_Tp, _Compare, _Allocator>::__tree(const __tree& __t)
1330-
: __begin_node_(),
1426+
: __begin_node_(__end_node()),
13311427
__node_alloc_(__node_traits::select_on_container_copy_construction(__t.__node_alloc())),
13321428
__size_(0),
13331429
__value_comp_(__t.value_comp()) {
1334-
__begin_node_ = __end_node();
1430+
if (__t.size() == 0)
1431+
return;
1432+
1433+
*__root_ptr() = static_cast<__node_base_pointer>(__copy_construct_tree(__t.__root()));
1434+
__root()->__parent_ = __end_node();
1435+
__begin_node_ = static_cast<__end_node_pointer>(std::__tree_min(static_cast<__node_base_pointer>(__end_node())));
1436+
__size_ = __t.size();
13351437
}
13361438

13371439
template <class _Tp, class _Compare, class _Allocator>
@@ -1430,13 +1532,7 @@ __tree<_Tp, _Compare, _Allocator>::~__tree() {
14301532

14311533
template <class _Tp, class _Compare, class _Allocator>
14321534
void __tree<_Tp, _Compare, _Allocator>::destroy(__node_pointer __nd) _NOEXCEPT {
1433-
if (__nd != nullptr) {
1434-
destroy(static_cast<__node_pointer>(__nd->__left_));
1435-
destroy(static_cast<__node_pointer>(__nd->__right_));
1436-
__node_allocator& __na = __node_alloc();
1437-
__node_traits::destroy(__na, std::addressof(__nd->__value_));
1438-
__node_traits::deallocate(__na, __nd, 1);
1439-
}
1535+
(__tree_deleter(__node_alloc_))(__nd);
14401536
}
14411537

14421538
template <class _Tp, class _Compare, class _Allocator>

libcxx/include/map

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ public:
970970
: map(from_range, std::forward<_Range>(__range), key_compare(), __a) {}
971971
# endif
972972

973-
_LIBCPP_HIDE_FROM_ABI map(const map& __m) : __tree_(__m.__tree_) { insert(__m.begin(), __m.end()); }
973+
_LIBCPP_HIDE_FROM_ABI map(const map& __m) = default;
974974

975975
_LIBCPP_HIDE_FROM_ABI map& operator=(const map& __m) = default;
976976

@@ -1637,11 +1637,7 @@ public:
16371637
: multimap(from_range, std::forward<_Range>(__range), key_compare(), __a) {}
16381638
# endif
16391639

1640-
_LIBCPP_HIDE_FROM_ABI multimap(const multimap& __m)
1641-
: __tree_(__m.__tree_.value_comp(),
1642-
__alloc_traits::select_on_container_copy_construction(__m.__tree_.__alloc())) {
1643-
insert(__m.begin(), __m.end());
1644-
}
1640+
_LIBCPP_HIDE_FROM_ABI multimap(const multimap& __m) = default;
16451641

16461642
_LIBCPP_HIDE_FROM_ABI multimap& operator=(const multimap& __m) = default;
16471643

libcxx/include/set

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ public:
660660
: set(from_range, std::forward<_Range>(__range), key_compare(), __a) {}
661661
# endif
662662

663-
_LIBCPP_HIDE_FROM_ABI set(const set& __s) : __tree_(__s.__tree_) { insert(__s.begin(), __s.end()); }
663+
_LIBCPP_HIDE_FROM_ABI set(const set& __s) = default;
664664

665665
_LIBCPP_HIDE_FROM_ABI set& operator=(const set& __s) = default;
666666

@@ -1119,11 +1119,7 @@ public:
11191119
: multiset(from_range, std::forward<_Range>(__range), key_compare(), __a) {}
11201120
# endif
11211121

1122-
_LIBCPP_HIDE_FROM_ABI multiset(const multiset& __s)
1123-
: __tree_(__s.__tree_.value_comp(),
1124-
__alloc_traits::select_on_container_copy_construction(__s.__tree_.__alloc())) {
1125-
insert(__s.begin(), __s.end());
1126-
}
1122+
_LIBCPP_HIDE_FROM_ABI multiset(const multiset& __s) = default;
11271123

11281124
_LIBCPP_HIDE_FROM_ABI multiset& operator=(const multiset& __s) = default;
11291125

libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ void associative_container_benchmarks(std::string container) {
151151
/////////////////////////
152152
// Assignment
153153
/////////////////////////
154-
bench("operator=(const&)", [=](auto& st) {
154+
bench("operator=(const&) (into cleared Container)", [=](auto& st) {
155155
const std::size_t size = st.range(0);
156156
std::vector<Value> in = make_value_types(generate_unique_keys(size));
157157
Container src(in.begin(), in.end());
@@ -172,6 +172,21 @@ void associative_container_benchmarks(std::string container) {
172172
}
173173
});
174174

175+
bench("operator=(const&) (into populated Container)", [=](auto& st) {
176+
const std::size_t size = st.range(0);
177+
std::vector<Value> in = make_value_types(generate_unique_keys(size));
178+
Container src(in.begin(), in.end());
179+
Container c[BatchSize];
180+
181+
while (st.KeepRunningBatch(BatchSize)) {
182+
for (std::size_t i = 0; i != BatchSize; ++i) {
183+
c[i] = src;
184+
benchmark::DoNotOptimize(c[i]);
185+
benchmark::ClobberMemory();
186+
}
187+
}
188+
});
189+
175190
/////////////////////////
176191
// Insertion
177192
/////////////////////////

libcxx/test/benchmarks/containers/associative/map.bench.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct support::adapt_operations<std::map<K, V>> {
2929

3030
int main(int argc, char** argv) {
3131
support::associative_container_benchmarks<std::map<int, int>>("std::map<int, int>");
32+
support::associative_container_benchmarks<std::map<std::string, int>>("std::map<std::string, int>");
3233

3334
benchmark::Initialize(&argc, argv);
3435
benchmark::RunSpecifiedBenchmarks();

libcxx/test/benchmarks/containers/associative/set.bench.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct support::adapt_operations<std::set<K>> {
2727

2828
int main(int argc, char** argv) {
2929
support::associative_container_benchmarks<std::set<int>>("std::set<int>");
30+
support::associative_container_benchmarks<std::set<std::string>>("std::set<std::string>");
3031

3132
benchmark::Initialize(&argc, argv);
3233
benchmark::RunSpecifiedBenchmarks();

0 commit comments

Comments
 (0)