New power of 2 hash policy

François Dumont frs.dumont@gmail.com
Mon Sep 28 19:16:00 GMT 2015


On 25/09/2015 15:28, Jonathan Wakely wrote:
> @@ -501,6 +503,129 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>     mutable std::size_t    _M_next_resize;
>>   };
>>
>> +  /// Range hashing function considering that second args is a power
>> of 2.
>
> Does this mean "assuming" not "considering"?

I assume yes.

>
>> +  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<std::size_t _N>
>> +    struct _NextPower2
>> +    {
>> +      static std::size_t
>> +      _Get(std::size_t __n)
>> +      {
>> +    std::size_t __next = _NextPower2<(_N >> 1)>::_Get(__n);
>> +    return __next |= __next >> _N;
>> +      }
>> +    };
>> +
>> +  template<>
>> +    struct _NextPower2<1>
>> +    {
>> +      static std::size_t
>> +      _Get(std::size_t __n)
>> +      { return __n |= __n >> 1; }
>> +    };
>
> This doesn't seem to return the next power of 2, it returns one less.
>
> _NextPower2<32>::_Get(2) returns 3, but 2 is already a power of 2.
> _NextPower2<32>::_Get(3) returns 3, but the next power of 2 is 4.


Yes, name is bad, that is just part of the algo you copy/paste below. I
review implementation to have _NextPower2 do all the algo.

>
>
> I don't think this needs to be a recursive template, it can simply be
> a function, can't it?

I wanted code to adapt to any sizeof(std::size_t) without relying on
some preprocessor checks. As you pointed out additional >> 32 on 32 bits
or >> 64 on 64 bits wouldn't hurt but the recursive template just make
sure that we don't do useless operations.

>
>
>> +  /// Rehash policy providing power of 2 bucket numbers. Ease modulo
>> +  /// 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).
>
> This says "no smaller than n" but it actually seems to guarantee
> "greater than n" because _NextPower2<>::_Get(n)+1 is 2n when n is a
> power of two.

yes but this function is calling _NextPower2<>::_Get(n - 1) + 1, there
is a minus one which make this comment valid as shown by newly
introduced test.

>
>> +    std::size_t
>> +    _M_next_bkt(std::size_t __n) const
>> +    {
>> +      constexpr auto __max_bkt
>> +    = (std::size_t(1) << (sizeof(std::size_t) * 8 - 1));
>> +
>> +      std::size_t __res
>> +    = _NextPower2<((sizeof(std::size_t) * 8) >> 1)>::_Get(--__n) + 1;
>
> You wouldn't need to add one to the result if the template actually
> returned a power of two!
>
>> +      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;
>> +      else
>> +    _M_next_resize
>> +      = __builtin_floor(__res * (long double)_M_max_load_factor);
>> +
>> +      return __res;
>> +    }
>
> What are the requirements for this function, "no smaller than n" or
> "greater than n"?

'No smaller than n' like stated in the comment. However for big n it is
not possible, even in the prime number based implementation. So I played
with _M_next_resize to make sure that _M_next_bkt won't be called again
as soon as the max bucket number has been reach.


>
> If "no smaller than n" is correct then the algorithm you want is
> "round up to nearest power of 2", which you can find here (I wrote
> this earlier this year for some reason I can't remember now):
>
> https://gitlab.com/redistd/redistd/blob/master/include/redi/bits.h
>
> The non-recursive version is only a valid constexpr function in C++14,
> but since you don't need a constexpr function you could just that,
> extended to handle 64-bit:
>
>  std::size_t
>  clp2(std::size_t n)
>  {
>    std::uint_least64_t x = n;
>    // 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);
>    x = x | (x >>32);
>    return x + 1;
>  }
>
> We could avoid the last shift when sizeof(size_t) == 32, I don't know
> if the optimisers will take care of that anyway.

This is indeed the algo I found by myself and that I adapted to work
with any sizeof(size_t).

Do you prefer the new version or do you want to stick a more explicit
version like the one you propose above. In this case is the last 32 bits
shift enough ? No 128 bits platform yet ?

François

-------------- next part --------------
A non-text attachment was scrubbed...
Name: hashtable.patch
Type: text/x-patch
Size: 17855 bytes
Desc: not available
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20150928/edaf303e/attachment.bin>


More information about the Libstdc++ mailing list