Skip to content

Commit acc3a62

Browse files
Revert "[libc++] Optimize __hash_table::erase(iterator, iterator) (#1… (#158769)
…52471)" This reverts commit e4eccd6. This was causing ASan failures in some situations involving unordered multimap containers. Details and a reproducer were posted on the original PR (#152471).
1 parent b2f3863 commit acc3a62

File tree

3 files changed

+28
-89
lines changed

3 files changed

+28
-89
lines changed

libcxx/docs/ReleaseNotes/22.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ Improvements and New Features
5757
has been improved by up to 3x
5858
- The performance of ``insert(iterator, iterator)`` of ``map``, ``set``, ``multimap`` and ``multiset`` has been improved
5959
by up to 2.5x
60-
- The performance of ``erase(iterator, iterator)`` in the unordered containers has been improved by up to 1.9x
6160
- The performance of ``map::insert_or_assign`` has been improved by up to 2x
6261

6362
- ``ofstream::write`` has been optimized to pass through large strings to system calls directly instead of copying them

libcxx/include/__hash_table

Lines changed: 28 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,21 +1036,7 @@ private:
10361036
}
10371037
_LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__hash_table&, false_type) _NOEXCEPT {}
10381038

1039-
_LIBCPP_HIDE_FROM_ABI void __deallocate_node(__node_pointer __nd) _NOEXCEPT {
1040-
auto& __alloc = __node_alloc();
1041-
__node_traits::destroy(__alloc, std::addressof(__nd->__get_value()));
1042-
std::__destroy_at(std::__to_address(__nd));
1043-
__node_traits::deallocate(__alloc, __nd, 1);
1044-
}
1045-
1046-
_LIBCPP_HIDE_FROM_ABI void __deallocate_node_list(__next_pointer __np) _NOEXCEPT {
1047-
while (__np != nullptr) {
1048-
__next_pointer __next = __np->__next_;
1049-
__deallocate_node(__np->__upcast());
1050-
__np = __next;
1051-
}
1052-
}
1053-
1039+
_LIBCPP_HIDE_FROM_ABI void __deallocate_node(__next_pointer __np) _NOEXCEPT;
10541040
_LIBCPP_HIDE_FROM_ABI __next_pointer __detach() _NOEXCEPT;
10551041

10561042
template <class _From, class _ValueT = _Tp, __enable_if_t<__is_hash_value_type<_ValueT>::value, int> = 0>
@@ -1188,7 +1174,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::~__hash_table() {
11881174
static_assert(is_copy_constructible<hasher>::value, "Hasher must be copy-constructible.");
11891175
#endif
11901176

1191-
__deallocate_node_list(__first_node_.__next_);
1177+
__deallocate_node(__first_node_.__next_);
11921178
}
11931179

11941180
template <class _Tp, class _Hash, class _Equal, class _Alloc>
@@ -1264,7 +1250,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::operator=(const __hash_table& __other)
12641250
// At this point we either have consumed the whole incoming hash table, or we don't have any more nodes to reuse in
12651251
// the destination. Either continue with constructing new nodes, or deallocate the left over nodes.
12661252
if (__own_iter->__next_) {
1267-
__deallocate_node_list(__own_iter->__next_);
1253+
__deallocate_node(__own_iter->__next_);
12681254
__own_iter->__next_ = nullptr;
12691255
} else {
12701256
__copy_construct(__other_iter, __own_iter, __current_chash);
@@ -1275,6 +1261,19 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::operator=(const __hash_table& __other)
12751261
return *this;
12761262
}
12771263

1264+
template <class _Tp, class _Hash, class _Equal, class _Alloc>
1265+
void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__deallocate_node(__next_pointer __np) _NOEXCEPT {
1266+
__node_allocator& __na = __node_alloc();
1267+
while (__np != nullptr) {
1268+
__next_pointer __next = __np->__next_;
1269+
__node_pointer __real_np = __np->__upcast();
1270+
__node_traits::destroy(__na, std::addressof(__real_np->__get_value()));
1271+
std::__destroy_at(std::addressof(*__real_np));
1272+
__node_traits::deallocate(__na, __real_np, 1);
1273+
__np = __next;
1274+
}
1275+
}
1276+
12781277
template <class _Tp, class _Hash, class _Equal, class _Alloc>
12791278
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::__next_pointer
12801279
__hash_table<_Tp, _Hash, _Equal, _Alloc>::__detach() _NOEXCEPT {
@@ -1330,11 +1329,11 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__move_assign(__hash_table& __u,
13301329
}
13311330
#if _LIBCPP_HAS_EXCEPTIONS
13321331
} catch (...) {
1333-
__deallocate_node_list(__cache);
1332+
__deallocate_node(__cache);
13341333
throw;
13351334
}
13361335
#endif // _LIBCPP_HAS_EXCEPTIONS
1337-
__deallocate_node_list(__cache);
1336+
__deallocate_node(__cache);
13381337
}
13391338
const_iterator __i = __u.begin();
13401339
while (__u.size() != 0)
@@ -1373,11 +1372,11 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__assign_unique(_InputIterator __
13731372
}
13741373
#if _LIBCPP_HAS_EXCEPTIONS
13751374
} catch (...) {
1376-
__deallocate_node_list(__cache);
1375+
__deallocate_node(__cache);
13771376
throw;
13781377
}
13791378
#endif // _LIBCPP_HAS_EXCEPTIONS
1380-
__deallocate_node_list(__cache);
1379+
__deallocate_node(__cache);
13811380
}
13821381
for (; __first != __last; ++__first)
13831382
__emplace_unique(*__first);
@@ -1403,11 +1402,11 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__assign_multi(_InputIterator __f
14031402
}
14041403
#if _LIBCPP_HAS_EXCEPTIONS
14051404
} catch (...) {
1406-
__deallocate_node_list(__cache);
1405+
__deallocate_node(__cache);
14071406
throw;
14081407
}
14091408
#endif // _LIBCPP_HAS_EXCEPTIONS
1410-
__deallocate_node_list(__cache);
1409+
__deallocate_node(__cache);
14111410
}
14121411
for (; __first != __last; ++__first)
14131412
__emplace_multi(*__first);
@@ -1440,7 +1439,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::end() const _NOEXCEPT {
14401439
template <class _Tp, class _Hash, class _Equal, class _Alloc>
14411440
void __hash_table<_Tp, _Hash, _Equal, _Alloc>::clear() _NOEXCEPT {
14421441
if (size() > 0) {
1443-
__deallocate_node_list(__first_node_.__next_);
1442+
__deallocate_node(__first_node_.__next_);
14441443
__first_node_.__next_ = nullptr;
14451444
size_type __bc = bucket_count();
14461445
for (size_type __i = 0; __i < __bc; ++__i)
@@ -1900,57 +1899,12 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __p) {
19001899
template <class _Tp, class _Hash, class _Equal, class _Alloc>
19011900
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator
19021901
__hash_table<_Tp, _Hash, _Equal, _Alloc>::erase(const_iterator __first, const_iterator __last) {
1903-
if (__first == __last)
1904-
return iterator(__last.__node_);
1905-
1906-
// current node
1907-
__next_pointer __current = __first.__node_;
1908-
size_type __bucket_count = bucket_count();
1909-
size_t __chash = std::__constrain_hash(__current->__hash(), __bucket_count);
1910-
// find previous node
1911-
__next_pointer __before_first = __bucket_list_[__chash];
1912-
for (; __before_first->__next_ != __current; __before_first = __before_first->__next_)
1913-
;
1914-
1915-
__next_pointer __last_node = __last.__node_;
1916-
1917-
// If __before_first is in the same bucket (i.e. the first element we erase is not the first in the bucket), clear
1918-
// this bucket first without re-linking it
1919-
if (__before_first != __first_node_.__ptr() &&
1920-
std::__constrain_hash(__before_first->__hash(), __bucket_count) == __chash) {
1921-
while (__current != __last_node) {
1922-
if (auto __next_chash = std::__constrain_hash(__current->__hash(), __bucket_count); __next_chash != __chash) {
1923-
__chash = __next_chash;
1924-
break;
1925-
}
1926-
auto __next = __current->__next_;
1927-
__deallocate_node(__current->__upcast());
1928-
__current = __next;
1929-
--__size_;
1930-
}
1902+
for (const_iterator __p = __first; __first != __last; __p = __first) {
1903+
++__first;
1904+
erase(__p);
19311905
}
1932-
1933-
while (__current != __last_node) {
1934-
auto __next = __current->__next_;
1935-
__deallocate_node(__current->__upcast());
1936-
__current = __next;
1937-
--__size_;
1938-
1939-
// When switching buckets, set the old bucket to be empty and update the next bucket to have __before_first as its
1940-
// before-first element
1941-
if (__next) {
1942-
if (auto __next_chash = std::__constrain_hash(__next->__hash(), __bucket_count); __next_chash != __chash) {
1943-
__bucket_list_[__chash] = nullptr;
1944-
__chash = __next_chash;
1945-
__bucket_list_[__chash] = __before_first;
1946-
}
1947-
}
1948-
}
1949-
1950-
// re-link __before_first with __last
1951-
__before_first->__next_ = __current;
1952-
1953-
return iterator(__last.__node_);
1906+
__next_pointer __np = __last.__node_;
1907+
return iterator(__np);
19541908
}
19551909

19561910
template <class _Tp, class _Hash, class _Equal, class _Alloc>

libcxx/test/std/containers/unord/unord.multimap/unord.multimap.modifiers/erase_range.pass.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,6 @@ int main(int, char**) {
9191
assert(c.size() == 0);
9292
assert(k == c.end());
9393
}
94-
{ // Make sure we're properly destroying the elements when erasing
95-
{ // When erasing part of a bucket
96-
std::unordered_multimap<int, std::string> map;
97-
map.insert(std::make_pair(1, "This is a long string to make sure ASan can detect a memory leak"));
98-
map.insert(std::make_pair(1, "This is another long string to make sure ASan can detect a memory leak"));
99-
map.erase(++map.begin(), map.end());
100-
}
101-
{ // When erasing the whole bucket
102-
std::unordered_multimap<int, std::string> map;
103-
map.insert(std::make_pair(1, "This is a long string to make sure ASan can detect a memory leak"));
104-
map.insert(std::make_pair(1, "This is another long string to make sure ASan can detect a memory leak"));
105-
map.erase(map.begin(), map.end());
106-
}
107-
}
10894
#if TEST_STD_VER >= 11
10995
{
11096
typedef std::unordered_multimap<int,

0 commit comments

Comments
 (0)