debug container mutex association
François Dumont
frs.dumont@gmail.com
Wed Sep 14 20:17:00 GMT 2016
On 14/09/2016 11:00, Jonathan Wakely wrote:
> On 13/09/16 22:37 +0200, François Dumont wrote:
>> Hi
>>
>> When I proposed to change std::hash for pointers my plan was to
>> use this approach for the debug containers. So here is the patch
>> leveraging on this technique to avoid going through _Hash_impl to
>> hash address and get mutex index from it. I think it is obious that
>> new access is faster so I didn't wrote a performance test to
>> demonstrate it.
>
> Is this actually a bottleneck that needs to be made faster?
>
> I know it's nice if Debug Mode isn't very slow, but you've already
> made it much better, and performance is not the primary goal of Debug
> Mode.
Sure but my approach is that if I can make something faster then I just
try. I considered that making this mode faster will allow its usage in
an even more extended way.
>
>> diff --git a/libstdc++-v3/config/abi/pre/gnu.ver
>> b/libstdc++-v3/config/abi/pre/gnu.ver
>> index 9b5bb23..c9a253e 100644
>> --- a/libstdc++-v3/config/abi/pre/gnu.ver
>> +++ b/libstdc++-v3/config/abi/pre/gnu.ver
>> @@ -1947,6 +1947,21 @@ GLIBCXX_3.4.23 {
>> _ZNSsC[12]ERKSs[jmy]RKSaIcE;
>> _ZNSbIwSt11char_traitsIwESaIwEEC[12]ERKS2_mRKS1_;
>>
>> + # __gnu_debug::_Safe_sequence_base
>> + _ZN11__gnu_debug19_Safe_sequence_base18_M_detach_singularEm;
>
> The 'm' here should be [jmy] because std::size_t mangles differently
> on different targets.
>
>> + _ZN11__gnu_debug19_Safe_sequence_base22_M_revalidate_singularEm;
>> + _ZN11__gnu_debug19_Safe_sequence_base12_M_get_mutexEm;
>> + _ZN11__gnu_debug19_Safe_sequence_base7_M_swapERS0_m;
>> +
>> + # __gnu_debug::_Safe_iterator_base
>> +
>> _ZN11__gnu_debug19_Safe_iterator_base9_M_attachEPNS_19_Safe_sequence_baseEbm;
>> + _ZN11__gnu_debug19_Safe_iterator_base9_M_detachEm;
>> +
>> + # __gnu_debug::_Safe_unordered_container_base and
>> _Safe_local_iterator_base
>> + _ZN11__gnu_debug30_Safe_unordered_container_base7_M_swapERS0_m;
>> +
>> _ZN11__gnu_debug25_Safe_local_iterator_base9_M_attachEPNS_19_Safe_sequence_baseEbm;
>> + _ZN11__gnu_debug25_Safe_local_iterator_base9_M_detachEm;
>> +
>> } GLIBCXX_3.4.22;
>
> It's unfortunate to have to export and maintain new symbols when we
> don't offer the same level of ABI guarantee for Debug Mode :-(
Yes, I have been told that Debug ABI compatibility was not guaranteed
but at the same time if we don't do so abi-check is failing. Couldn't we
have a special section in gnu.ver for unversioned symbols like those ?
>
>> @@ -174,6 +191,18 @@ namespace __gnu_debug
>> const _Safe_iterator_base* __last)
>> { return __first->_M_can_compare(*__last); }
>>
>> + // Compute power of 2 not greater than __n.
>> + template<size_t __n>
>> + struct _Lowest_power_of_two
>> + {
>> + static const size_t __val
>> + = _Lowest_power_of_two< (__n >> 1) >::__val + 1;
>> + };
>> +
>> + template<>
>> + struct _Lowest_power_of_two<1>
>> + { static const size_t __val = 1; };
>
> The lowest power of two not greater than 1 is 2^0, but this gives the
> result 1.
>
> Similarly, the lowest power of two not greater than 2 is 2^1, i.e. 1.
Indeed, when writing the test case I decided to return 1 rather than 0
for the _Lowest_power_of_two<1> specialization.It gave a better
container instance - mutex mapping. But I forgot to change its name.
>
> And the name is misleading, it doesn't compute a power of two, it
> computes the base-2 logarithm (then adds one), so it's a compile-time
> version of floor(log2(n))+1.
>
>> @@ -123,6 +125,36 @@ namespace __gnu_debug
>> template<typename _Predicate>
>> void
>> _M_transfer_from_if(_Safe_sequence& __from, _Predicate __pred);
>> +
>> + static _GLIBCXX_CONSTEXPR std::size_t
>> + _S_alignment() _GLIBCXX_NOEXCEPT
>> + { return _Lowest_power_of_two<__alignof(_Sequence)>::__val; }
>
> This function is called "alignment" but it returns log2(alignof(T))+1
Yes, I wasn't proud about the naming.
>
> Does it need to be constexpr? Is that for std::array?
No, it is not used in std::array context. I just considered that it
could be constexpr.
>
> You can get the same result without _Lowest_power_of_two:
>
> static _GLIBCXX_CONSTEXPR size_t
> _S_alignbits() _GLIBCXX_NOEXCEPT
> { return __builtin_ctz(__alignof(_Sequence)) + 1; }
>
> But I'm not convinced the +1 is correct, see below.
>
>> @@ -46,15 +47,30 @@ namespace
>> /** Returns different instances of __mutex depending on the passed
>> address
>> * in order to limit contention without breaking current library
>> binary
>> * compatibility. */
>> + const size_t mask = 0xf;
>> +
>> __gnu_cxx::__mutex&
>> - get_safe_base_mutex(void* __address)
>> + get_mutex(size_t index)
>> {
>> - const size_t mask = 0xf;
>> static __gnu_cxx::__mutex safe_base_mutex[mask + 1];
>> - const size_t index = _Hash_impl::hash(__address) & mask;
>> return safe_base_mutex[index];
>> }
>
> N.B. https://gcc.gnu.org/PR71312
Ok, debug mode is also impacted. Shouldn't the alignment be set on the
__mutex type directly ?
>
>
>>
>> + __gnu_cxx::__mutex&
>> + get_safe_base_mutex(void* address)
>> + {
>> + const size_t index = _Hash_impl::hash(address) & mask;
>> + return get_mutex(index);
>> + }
>> +
>> + __gnu_cxx::__mutex&
>> + get_safe_base_mutex(void* address, std::size_t alignment)
>> + {
>> + const size_t index
>> + = (reinterpret_cast<size_t>(address) >> alignment) & mask;
>> + return get_mutex(index);
>> + }
>
> Since the "alignment" value (which is really log2(align)+1) is always
> a positive value that means we always shift the address. But that
> means we drop the least significant bit of all pointers, even when the
> type has alignof(T)==1 and so all bits in the pointer are significant.
> Is that intentional? Is the idea to only drop bits that are always
> zero?
Yes, to make sure we get use of all available mutexes.
>
> By changing the algorithm we use to map an address to a mutex do we
> allow programs to get two different mutexes for the same object? That
> can only happen when some objects are compiled with an older GCC and
> so still use the old _M_get_mutex() function that doesn't take an
> argument. We require all objects to be rebuilt with the same compiler
> for Debug Mode (but maybe not if using __gnu_debug::vector etc.
> explicitly in normal mode?) so that's not a big deal. But it's a pity
> we have to keep the old symbols around if using them is unsafe.
Yes, it could happen even if very unlikely. I would also prefer to get
rid of former symbols but can we ?
>
>> void
>> swap_its(__gnu_debug::_Safe_sequence_base& __lhs,
>> __gnu_debug::_Safe_iterator_base*& __lhs_its,
>> @@ -70,8 +86,8 @@ namespace
>> }
>>
>> void
>> - swap_seq(__gnu_debug::_Safe_sequence_base& __lhs,
>> - __gnu_debug::_Safe_sequence_base& __rhs)
>> + swap_seq_single(__gnu_debug::_Safe_sequence_base& __lhs,
>> + __gnu_debug::_Safe_sequence_base& __rhs)
>> {
>> swap(__lhs._M_version, __rhs._M_version);
>> swap_its(__lhs, __lhs._M_iterators,
>> @@ -81,10 +97,41 @@ namespace
>> }
>>
>> void
>> - swap_ucont(__gnu_debug::_Safe_unordered_container_base& __lhs,
>> - __gnu_debug::_Safe_unordered_container_base& __rhs)
>> + lock_and_run(__gnu_cxx::__mutex& lhs_mutex,
>> + __gnu_cxx::__mutex& rhs_mutex,
>> + std::function<void()> action)
>
> Do all calls to this fit into the std::function small-object buffer,
> so we don't need to allocate memory for it?
>
I didn't consider this. I thought it was a nice way to avoid making
lock_and_run a template function to take the lambda.
Reading this I realize that we could perhaps acheive the same without
touching as much code. __alignof of container types will always give the
same value on a give target no ? Does it depends on the type used to
instantiate the container ? Isn't there some macro giving the target
alignment that I could use ?
François
More information about the Libstdc++
mailing list