New hashtable power 2 rehash policy
François Dumont
frs.dumont@gmail.com
Sat May 14 16:14:00 GMT 2016
Le 28/04/2016 12:22, Jonathan Wakely a écrit :
> On 23/04/16 10:27 +0200, François Dumont wrote:
>> Hi
>>
>> Here is a patch to introduce a new power of 2 based rehash policy.
>> It enhances performance as it avoids modulo float operations. I have
>> updated performance benches and here is the result:
>>
> Thanks, now that we're back in stage 1 we can make this change.
>
>
>>
>> 2016-04-22 François Dumont <fdumont@gcc.gnu.org>
>>
>> * include/bits/hashtable_policy.h
>> (_Prime_rehash_policy::__has_load_factor): New. Mark rehash policy
>> having load factor management.
>> (_Mask_range_hashing): New.
>> (_NextPower2<size_t>): New.
>> (_Power2_rehash_policy): New.
>> (_Inserts<>): Remove last template parameter _Unique_keys. Use the
>> same
>> implementation when keys are unique no matter if iterators are
>> constant
>> or not.
>
> I found this change description confusing, because it's really using
> the same implementation whether keys are unique or not, but this says
> "Use the same implementation whether iterators are constant or not".
>
> Shouldn't it be "Use the same implementation when iterators are
> constant, no matter if keys are unique or not" ?
>
> Maybe this would be clearer:
>
> (_Inserts<>): Remove last template parameter, _Unique_keys, so
> that partial specializations only depend on whether iterators are
> constant or not.
Yes, sorry, I prepared this patch a long time ago and didn't reconsider
it in enough details.
>
>> + struct _Mask_range_hashing
>> + {
>> + typedef std::size_t first_argument_type;
>> + typedef std::size_t second_argument_type;
>> + typedef std::size_t result_type;
>> +
>> + result_type
>> + operator()(first_argument_type __num,
>> + second_argument_type __den) const noexcept
>> + { return __num & (__den - 1); }
>> + };
>> +
>> +
>> + /// Helper type to compute next power of 2.
>> + template<typename _SizeT,
>> + std::size_t _N = sizeof(_SizeT) << 2, bool _Increment = true>
>> + struct _NextPower2
>> + {
>> + static _SizeT
>> + _Get(_SizeT __n)
>> + {
>> + _SizeT __next = _NextPower2<_SizeT, (_N >> 1), false>::_Get(__n);
>> + __next |= __next >> _N;
>> + if (_Increment)
>> + ++__next;
>> +
>> + return __next;
>> + }
>> + };
>> +
>> + template<typename _SizeT>
>> + struct _NextPower2<_SizeT, 1, false>
>> + {
>> + static _SizeT
>> + _Get(_SizeT __n)
>> + {
>> + --__n;
>> + return __n |= __n >> 1;
>> + }
>> + };
>
> What's the reason to keep this recursive template instead of using a
> simple function like the clp2() we discussed? A simple function (which
> could be _GLIBCXX14_CONSTEXPR) compiles faster, and produces similar
> object code for the default -std=gnu++14 mode. And it doesn't require
> six calls to _NextPower2::_Get to calculate the result.
I though we could have a wide variety of std::size_t definition on
different platforms. If we just need to consider 32/64 bits then it is
fine. Note that I thought that gcc would optimize away the calls to
_NextPower2::_Get.
>
> If you're worried about the final shift being unnecessary on 32-bit
> you can use the preprocessor, something like:
>
> _GLIBCXX14_CONSTEXPR
> std::size_t
> __clp2(std::size_t n)
> {
> #if __SIZEOF_SIZE_T__ >= 8
> std::uint_fast64_t x = n;
> #else
> std::uint_fast32_t x = n;
> #endif
> // Algorithm from Hacker's Delight, Figure 3-3.
> x = x - 1;
> x = x | (x >> 1);
> x = x | (x >> 2);
> x = x | (x >> 4);
> x = x | (x >> 8);
> x = x | (x >>16);
> #if __SIZEOF_SIZE_T__ >= 8
> x = x | (x >>32);
> #endif
> return x + 1;
> }
>
> I don't think we need to worry about 128-bit integers, because that
> would be a ridiculous number of buckets anyway. We can just limit
> __max_bkt so we don't have to deal with more than 2^63 buckets.
Adopted.
>> + /// operations.
>> + struct _Power2_rehash_policy
>> + {
>> + using __has_load_factor = std::true_type;
>> +
>> + _Power2_rehash_policy(float __z = 1.0) noexcept
>> + : _M_max_load_factor(__z), _M_next_resize(0) { }
>> +
>> + float
>> + max_load_factor() const noexcept
>> + { return _M_max_load_factor; }
>> +
>> + // Return a bucket size no smaller than n (as long as n is not
>> above the
>> + // highest power of 2).
>> + std::size_t
>> + _M_next_bkt(std::size_t __n) const
>> + {
>> + constexpr auto __max_bkt
>> + = std::size_t(1) << ((sizeof(std::size_t) << 3) - 1);
>
> Should this be sizeof(size_t) * __CHAR_BITS__ instead of << 3 ?
> Otherwise targets with char wider than 8 bits would get a smaller
> maximum number of buckets, even if they have 64-bit size_t.
sizeof() returns number of char elements in the given type ? I thought
it was the number of bytes and that a byte was always 8 bits. But I know
that you know your stuff so fixed in the new patch.
>
> So, including the suggestion above to limit it to 2^63, it would be:
>
> constexpr size_t __max_width = std::min(sizeof(size_t), 8);
> constexpr auto __max_bkt
> = std::size_t(1) << (__max_width * __CHAR_BITS__ - 1);
Ok,I just had to introduce a _GLIBCXX14_USE_CONSTEXPR to be able to
define this expression as constexpr.
>
>> +
>> + std::size_t __res = _NextPower2<std::size_t>::_Get(__n);
>> +
>> + if (__res == 0)
>> + __res = __max_bkt;
>> +
>> + if (__res == __max_bkt)
>> + // Set next resize to the max value so that we never try to
>> rehash again
>> + // as we already reach the biggest possible bucket number.
>> + // Note that it might result in max_load_factor not being
>> respected.
>> + _M_next_resize = std::size_t(0) - 1;
>
> This can be simplified to size_t(-1), there's no need for arithmetic.
This was my mental representation of it, size_t(-1) adopted.
>
>
>> @@ -775,8 +907,7 @@
>> typename _ExtractKey, typename _Equal,
>> typename _H1, typename _H2, typename _Hash,
>> typename _RehashPolicy, typename _Traits,
>> - bool _Constant_iterators = _Traits::__constant_iterators::value,
>> - bool _Unique_keys = _Traits::__unique_keys::value>
>> + bool _Constant_iterators = _Traits::__constant_iterators::value>
>> struct _Insert;
>>
>> /// Specialization.
>
> (I'm sure I've said it before, but having 10+ template parameters here
> makes me sad).
Me too but there are quite some work to reduce it. It would sure be a
very interesting design change to work on.
I took the time to adapt a number of test case to also cover this new
hash policy.
New patch attached, tested under linux x86_64.
François
-------------- next part --------------
A non-text attachment was scrubbed...
Name: hashtable.patch
Type: text/x-patch
Size: 26798 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20160514/67849b89/attachment.bin>
More information about the Libstdc++
mailing list