On Sat, 21 Feb 2026 at 17:40 +0100, François Dumont wrote:
Hi
libstdc++: [_GLIBCXX_DEBUG] Reduce unordered containers mutex
locks/unlocks
The unordered containers have 2 types of iterators, the usual ones
and the
local_iterator to iterate through a given bucket. In
_GLIBCXX_DEBUG mode there
are then 4 lists of iterators, 2 for iterator/const_iterator and 2 for
local_iterator/const_local_iterator.
This patch is making sure that the unordered container's mutex is
only lock/unlock
1 time when those lists of iterators needed to be iterate to
invalidate some of
them.
Also remove calls to _M_check_rehashed after erase operations.
Standard do not permit
to rehash on erase operation so we will never implement it.
libstdc++-v3/ChangeLog
* include/debug/safe_unordered_container.h
(__gnu_debug::_Safe_unordered_container<>::_M_invalidate_all()): Lock mutex
You can omit the "__gnu_debug::" here since everything in the file is
in that namespace anyway, so it's implied. Just referring to
_Safe_unordered_container is unambiguous.
It's a primary template not a specialization, so the "<>" doesn't add
any value.
And you can omit the "()" at the end, since that's also implied.
So just "_Safe_unordered_container::_M_invalidate_all"
while calling _M_invalidate_if and _M_invalidate_locals.
(_Safe_unordered_container<>::_M_invalidate_all_if<_Pred>()): New.
This is not the signature of the function, it takes a parameter, so
showing it as "()" is wrong, and the "<>" is unnecessary.
So just "_Safe_unordered_container::_M_invalidate_all_if"
(_Safe_unordered_container<>::_M_invalidate_all_single_if<_Pred>()): New.
Same here.
(_Safe_unordered_container<>::struct _InvalidatePred<>): New.
The "<>" and "struct " parts are not adding any value. You can say
"New class template" if you want to make it clear it's a template.
(_Safe_unordered_container<>::_M_invalidate<_It>(_It)): New.
_Safe_unordered_container::_M_invalidate
(_Safe_unordered_container<>::_M_invalidate_single<_It>(_It)): New.
_Safe_unordered_container::_M_invalidate_single
* include/debug/safe_unordered_container.tcc
(_Safe_unordered_container<>::_M_invalidate_if<_Pred>(_Pred)): Remove lock.
(_Safe_unordered_container<>::_M_invalidate_local_if<_Pred>(_Pred)):
Same here.
Remove lock.
* include/debug/unordered_map
(unordered_map<>::erase(const_iterator, const_iterator)):
Here the "(const_iterator, const_iterator)" does add value, because
it's an overloaded function. But "<>" doesn't add value.
Add lock before loop
for iterator invalidation. Use _M_invalidate_single for
invalidation. Remove
_M_check_rehashed call.
(unordered_map<>::_M_self()): New.
(unordered_map<>::_M_invalidate(_Base_const_iterator)): Remove.
(unordered_map<>::_M_erase(_Base_const_iterator)): Adapt. Remove
_M_check_rehashed
call.
(unordered_multimap<>::erase(const key_type&)): Add lock
before loop for iterator
invalidation. Use _M_invalidate_single for invalidation.
Remove _M_check_rehashed
call.
(unordered_multimap<>::erase(const_iterator,
const_iterator)): Likewise.
(unordered_multimap<>::_M_self()): New.
(unordered_multimap<>::_M_invalidate(_Base_const_iterator)): Remove.
(unordered_multimap<>::_M_erase(_Base_const_iterator)): Adapt. Remove
_M_check_rehashed
call.
* include/debug/unordered_set
(unordered_set<>::erase(const_iterator, const_iterator)):
Add lock before loop
for iterator invalidation. Use _M_invalidate_single for
invalidation. Remove
_M_check_rehashed call.
(unordered_set<>::_M_self()): New.
(unordered_set<>::_M_invalidate(_Base_const_iterator)): Remove.
(unordered_set<>::_M_erase(_Base_const_iterator)): Adapt. Remove
_M_check_rehashed
call.
(unordered_multiset<>::erase(const key_type&)): Add lock
before loop for iterator
invalidation. Use _M_invalidate_single for invalidation.
Remove _M_check_rehashed
call.
(unordered_multiset<>::erase(const_iterator,
const_iterator)): Likewise.
(unordered_multiset<>::_M_self()): New.
(unordered_multiset<>::_M_invalidate(_Base_const_iterator)): Remove.
(unordered_multiset<>::_M_erase(_Base_const_iterator)): Adapt. Remove
_M_check_rehashed
call.
All 23_containers/unordered_* tests run under Linux x64_86 in
_GLIBCXX_DEBUG mode.
Note that patch was generated with a git diff -b option.
Ok to commit ?
François
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h
b/libstdc++-v3/include/debug/safe_unordered_container.h
index e76d60d2605..0571efaba49 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -57,7 +57,6 @@ namespace __gnu_debug
template<typename _Container>
class _Safe_unordered_container : public _Safe_unordered_container_base
{
- private:
_Container&
_M_cont() noexcept
{ return *static_cast<_Container*>(this); }
@@ -66,17 +65,17 @@ namespace __gnu_debug
_M_self() const
{ return this; }
- protected:
void
_M_invalidate_locals()
{
auto __local_end = _M_cont()._M_base().cend(0);
- this->_M_invalidate_local_if(
+ _M_invalidate_local_if(
[__local_end](__decltype(__local_end) __it)
{ return __it != __local_end; });
}
#if __cplusplus > 201402L
+ protected:
template<typename _ExtractKey, typename _Source>
struct _UContInvalidatePred
{
@@ -130,10 +129,7 @@ namespace __gnu_debug
if (__size == 0)
_M_source._M_invalidate_all();
else
- {
- _M_source._M_invalidate_if(_M_pred);
- _M_source._M_invalidate_local_if(_M_pred);
- }
+ _M_source._M_invalidate_all_if(_M_pred);
}
__catch(...)
{
@@ -169,12 +165,61 @@ namespace __gnu_debug
void
_M_invalidate_all()
{
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
auto __end = _M_cont()._M_base().cend();
- this->_M_invalidate_if([__end](__decltype(__end) __it)
+ _M_invalidate_if([__end](__decltype(__end) __it)
{ return __it != __end; });
_M_invalidate_locals();
}
+ template<typename _Predicate>
+ void
+ _M_invalidate_all_if(_Predicate __pred)
+ {
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
+ _M_invalidate_all_single_if(__pred);
+ }
+
+ protected:
+ template<typename _Predicate>
+ void
+ _M_invalidate_all_single_if(_Predicate __pred)
+ {
+ _M_invalidate_if(__pred);
+ _M_invalidate_local_if(__pred);
+ }
+
+ template<typename _VictimIt>
+ struct _InvalidatePred
+ {
+ _InvalidatePred(_VictimIt __victim)
+ : _M_victim(__victim) { }
+
+ template<typename _Iterator>
+ bool
+ operator()(_Iterator __it) const
+ { return __it == _M_victim; }
+
+ _VictimIt _M_victim;
+ };
+
+ template<typename _VictimIt>
+ void
+ _M_invalidate(_VictimIt __victim)
Does this need to be a function template? Could its parameter just be
_Base_const_iterator instead?
And then _InvalidatePred::_M_victim could be the same type, and that
doesn't need to be a class template.
+ {
+ _InvalidatePred<_VictimIt> __pred(__victim);
+ _M_invalidate_all_if(__pred);
+ }
+
+ template<typename _VictimIt>
+ void
+ _M_invalidate_single(_VictimIt __victim)
+ {
+ _InvalidatePred<_VictimIt> __pred(__victim);
+ _M_invalidate_all_single_if(__pred);
+ }
+
+ private:
/** Invalidates all iterators @c x that reference this container,
are not singular, and for which @c __pred(x) returns @c
true. @c __pred will be invoked with the normal iterators nested
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.tcc
b/libstdc++-v3/include/debug/safe_unordered_container.tcc
index f60761534c8..6f2975116c7 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.tcc
+++ b/libstdc++-v3/include/debug/safe_unordered_container.tcc
@@ -40,7 +40,6 @@ namespace __gnu_debug
typedef typename _Container::iterator iterator;
typedef typename _Container::const_iterator const_iterator;
- __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
I find the changes in this file make the code more confusing. You've
added new member functions with "single" in the name to do the work without
locking the mutex, but then you've changed _M_invalidate_if and
_M_invalidate_local_if to not lock the mutex. But they don't have
"single" in the name. Without checking the code carefully, I would
assume that _M_invalidate_if acquires the lock, because that's what
the naming convention in safe_unordered_container.h seems to imply.
I think adding the new "single" functions is what causes this
confusion, because the names are not used consistently for all
non-locking functions. The names can also be misread so that
"invalidate single" looks like it invalidates a single iterator. We
have "invalidate all" to invalidate all iterators, and "invalidate
single" to invalidate a single iterator, right?! ... no, that's wrong.
And we also have "invalidate all single"!
I know we already use "_single" as a suffix in _Safe_base but I think
here it is confusing.
And one of those new "single" functions doesn't seem necessary at all:
_M_invalidate_all_single_if is just two lines, and is only used in two
places. You could just write those two lines inline where it's called.
That gets rid of _M_invalidate_all_single_if.
_M_invalidate_all_if is also only used in two places, so that can be
inlined into its callers too.
So now there are only two new member functions, _M_invalidate(victim)
and _M_invalidate_single(victim), one which acquires a lock and one
which assuems the caller has already acquired a lock. They could be
written like this:
void
_M_invalidate(_Base_const_iterator __victim)
{
__gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
_M_invalidate(__victim, sentry);
}
// Caller must hold lock
void
_M_invalidate(_Base_const_iterator __victim,
const __gnu_cxx::__scoped_lock&)
{
auto __pred = [__victim](_Base_const_iterator __it) {
return __it == __victim;
};
_M_invalidate_if(__pred);
_M_invalidate_local_if(__pred);
}
And then callers that have already taken the lock call the second
overload.
This also removes the need for _InvalidatePred.
So this avoids adding several new member functions and a new class
template, and avoids the naming inconsistency for when "single" is
used or not.
For the two function which no longer acquire the lock
(_M_invalidate_if and _M_invalidate_local_if) please add a similar
"Caller must hold lock" comment to their declarations in the header.
for (_Safe_iterator_base* __iter = _M_iterators; __iter;)
{
iterator* __victim = static_cast<iterator*>(__iter);
@@ -72,7 +71,6 @@ namespace __gnu_debug
typedef typename _Container::local_iterator local_iterator;
typedef typename _Container::const_local_iterator const_local_iterator;
- __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (_Safe_iterator_base* __iter = _M_local_iterators; __iter;)
{
local_iterator* __victim = static_cast<local_iterator*>(__iter);
diff --git a/libstdc++-v3/include/debug/unordered_map
b/libstdc++-v3/include/debug/unordered_map
index 4bde18c917b..30953417edc 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -738,18 +738,19 @@ namespace __debug
erase(const_iterator __first, const_iterator __last)
{
__glibcxx_check_erase_range(__first, __last);
+ {
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __tmp = __first.base(); __tmp != __last.base(); ++__tmp)
{
_GLIBCXX_DEBUG_VERIFY(__tmp != _Base::cend(),
_M_message(__gnu_debug::__msg_valid_range)
._M_iterator(__first, "first")
._M_iterator(__last, "last"));
- _M_invalidate(__tmp);
+ this->_M_invalidate_single(__tmp);
+ }
}
- size_type __bucket_count = this->bucket_count();
auto __next = _Base::erase(__first.base(), __last.base());
- _M_check_rehashed(__bucket_count);
return { __next, this };
}
@@ -763,6 +764,10 @@ namespace __debug
_M_base() const noexcept { return *this; }
private:
+ const unordered_map*
+ _M_self() const noexcept
+ { return this; }
+
void
_M_check_rehashed(size_type __prev_count)
{
@@ -770,31 +775,18 @@ namespace __debug
this->_M_invalidate_all();
}
- void
- _M_invalidate(_Base_const_iterator __victim)
- {
- this->_M_invalidate_if(
- [__victim](_Base_const_iterator __it) { return __it == __victim; });
- this->_M_invalidate_local_if(
- [__victim](_Base_const_local_iterator __it)
- { return __it == __victim; });
- }
-
_Base_iterator
_M_erase(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
- size_type __bucket_count = this->bucket_count();
- _Base_iterator __next = _Base::erase(__victim);
- _M_check_rehashed(__bucket_count);
- return __next;
+ this->_M_invalidate(__victim);
+ return _Base::erase(__victim);
}
#ifdef __glibcxx_node_extract // >= C++17 && HOSTED
node_type
_M_extract(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
+ this->_M_invalidate(__victim);
return _Base::extract(__victim);
}
#endif
@@ -1497,16 +1489,15 @@ namespace __debug
erase(const key_type& __key)
{
size_type __ret(0);
- size_type __bucket_count = this->bucket_count();
auto __pair = _Base::equal_range(__key);
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __victim = __pair.first; __victim != __pair.second;)
{
- _M_invalidate(__victim);
+ this->_M_invalidate_single(__victim);
__victim = _Base::erase(__victim);
++__ret;
}
- _M_check_rehashed(__bucket_count);
return __ret;
}
@@ -1535,18 +1526,19 @@ namespace __debug
erase(const_iterator __first, const_iterator __last)
{
__glibcxx_check_erase_range(__first, __last);
+ {
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __tmp = __first.base(); __tmp != __last.base(); ++__tmp)
{
_GLIBCXX_DEBUG_VERIFY(__tmp != _Base::cend(),
_M_message(__gnu_debug::__msg_valid_range)
._M_iterator(__first, "first")
._M_iterator(__last, "last"));
- _M_invalidate(__tmp);
+ this->_M_invalidate_single(__tmp);
+ }
}
- size_type __bucket_count = this->bucket_count();
auto __next = _Base::erase(__first.base(), __last.base());
- _M_check_rehashed(__bucket_count);
return { __next, this };
}
@@ -1560,6 +1552,10 @@ namespace __debug
_M_base() const noexcept { return *this; }
private:
+ const unordered_multimap*
+ _M_self() const noexcept
+ { return this; }
+
void
_M_check_rehashed(size_type __prev_count)
{
@@ -1567,31 +1563,18 @@ namespace __debug
this->_M_invalidate_all();
}
- void
- _M_invalidate(_Base_const_iterator __victim)
- {
- this->_M_invalidate_if(
- [__victim](_Base_const_iterator __it) { return __it == __victim; });
- this->_M_invalidate_local_if(
- [__victim](_Base_const_local_iterator __it)
- { return __it == __victim; });
- }
-
_Base_iterator
_M_erase(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
- size_type __bucket_count = this->bucket_count();
- _Base_iterator __next = _Base::erase(__victim);
- _M_check_rehashed(__bucket_count);
- return __next;
+ this->_M_invalidate(__victim);
+ return _Base::erase(__victim);
}
#ifdef __glibcxx_node_extract // >= C++17 && HOSTED
node_type
_M_extract(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
+ this->_M_invalidate(__victim);
return _Base::extract(__victim);
}
#endif
diff --git a/libstdc++-v3/include/debug/unordered_set
b/libstdc++-v3/include/debug/unordered_set
index de999a76890..cc55d15f7be 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -623,18 +623,19 @@ namespace __debug
erase(const_iterator __first, const_iterator __last)
{
__glibcxx_check_erase_range(__first, __last);
+ {
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __tmp = __first.base(); __tmp != __last.base(); ++__tmp)
{
_GLIBCXX_DEBUG_VERIFY(__tmp != _Base::cend(),
_M_message(__gnu_debug::__msg_valid_range)
._M_iterator(__first, "first")
._M_iterator(__last, "last"));
- _M_invalidate(__tmp);
+ this->_M_invalidate_single(__tmp);
+ }
}
- size_type __bucket_count = this->bucket_count();
auto __next = _Base::erase(__first.base(), __last.base());
- _M_check_rehashed(__bucket_count);
return { __next, this };
}
@@ -645,6 +646,10 @@ namespace __debug
_M_base() const noexcept { return *this; }
private:
+ const unordered_set*
+ _M_self() const noexcept
+ { return this; }
+
void
_M_check_rehashed(size_type __prev_count)
{
@@ -652,31 +657,18 @@ namespace __debug
this->_M_invalidate_all();
}
- void
- _M_invalidate(_Base_const_iterator __victim)
- {
- this->_M_invalidate_if(
- [__victim](_Base_const_iterator __it) { return __it == __victim; });
- this->_M_invalidate_local_if(
- [__victim](_Base_const_local_iterator __it)
- { return __it == __victim; });
- }
-
_Base_iterator
_M_erase(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
- size_type __bucket_count = this->bucket_count();
- _Base_iterator __next = _Base::erase(__victim);
- _M_check_rehashed(__bucket_count);
- return __next;
+ this->_M_invalidate(__victim);
+ return _Base::erase(__victim);
}
#ifdef __glibcxx_node_extract // >= C++17 && HOSTED
node_type
_M_extract(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
+ this->_M_invalidate(__victim);
return _Base::extract(__victim);
}
#endif
@@ -1320,9 +1312,10 @@ namespace __debug
{
size_type __ret(0);
auto __pair = _Base::equal_range(__key);
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __victim = __pair.first; __victim != __pair.second;)
{
- _M_invalidate(__victim);
+ this->_M_invalidate_single(__victim);
__victim = _Base::erase(__victim);
++__ret;
}
@@ -1355,14 +1348,18 @@ namespace __debug
erase(const_iterator __first, const_iterator __last)
{
__glibcxx_check_erase_range(__first, __last);
+ {
+ __gnu_cxx::__scoped_lock sentry(_M_self()->_M_get_mutex());
for (auto __tmp = __first.base(); __tmp != __last.base(); ++__tmp)
{
_GLIBCXX_DEBUG_VERIFY(__tmp != _Base::cend(),
_M_message(__gnu_debug::__msg_valid_range)
._M_iterator(__first, "first")
._M_iterator(__last, "last"));
- _M_invalidate(__tmp);
+ this->_M_invalidate_single(__tmp);
+ }
}
+
return { _Base::erase(__first.base(), __last.base()), this };
}
@@ -1373,6 +1370,10 @@ namespace __debug
_M_base() const noexcept { return *this; }
private:
+ const unordered_multiset*
+ _M_self() const noexcept
+ { return this; }
+
void
_M_check_rehashed(size_type __prev_count)
{
@@ -1380,31 +1381,18 @@ namespace __debug
this->_M_invalidate_all();
}
- void
- _M_invalidate(_Base_const_iterator __victim)
- {
- this->_M_invalidate_if(
- [__victim](_Base_const_iterator __it) { return __it == __victim; });
- this->_M_invalidate_local_if(
- [__victim](_Base_const_local_iterator __it)
- { return __it == __victim; });
- }
-
_Base_iterator
_M_erase(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
- size_type __bucket_count = this->bucket_count();
- _Base_iterator __next = _Base::erase(__victim);
- _M_check_rehashed(__bucket_count);
- return __next;
+ this->_M_invalidate(__victim);
+ return _Base::erase(__victim);
}
#ifdef __glibcxx_node_extract // >= C++17 && HOSTED
node_type
_M_extract(_Base_const_iterator __victim)
{
- _M_invalidate(__victim);
+ this->_M_invalidate(__victim);
return _Base::extract(__victim);
}
#endif