web-dev-qa-db-fra.com

Quelle est la fonction de hachage par défaut utilisée dans C ++ std :: unordered_map?

J'utilise

unordered_map<string, int>

et

unordered_map<int, int>

Quelle fonction de hachage est utilisée dans chaque cas et quelle est la probabilité de collision dans chaque cas? J'insérerai respectivement une chaîne unique et un entier unique comme clés.

Je suis intéressé à connaître l'algorithme de la fonction de hachage dans le cas des clés string et int et leurs statistiques de collision.

45
Medicine

L'objet fonction std::hash<> est utilisé.

Des spécialisations standard existent pour tous les types intégrés et certains autres types de bibliothèques standard tels que std::string et std::thread. Voir le lien pour la liste complète.

Pour d'autres types à utiliser dans un std::unordered_map, vous devrez vous spécialiser std::hash<> ou créez votre propre objet fonction.

Le risque de collision dépend entièrement de l'implémentation, mais compte tenu du fait que les nombres entiers sont limités entre une plage définie, alors que les chaînes sont théoriquement infiniment longues, je dirais qu'il y a une bien meilleure chance de collision avec des chaînes.

Quant à l'implémentation dans GCC, la spécialisation pour les types intégrés renvoie simplement le modèle de bits. Voici comment ils sont définis dans bits/functional_hash.h:

  /// Partial specializations for pointer types.
  template<typename _Tp>
    struct hash<_Tp*> : public __hash_base<size_t, _Tp*>
    {
      size_t
      operator()(_Tp* __p) const noexcept
      { return reinterpret_cast<size_t>(__p); }
    };

  // Explicit specializations for integer types.
#define _Cxx_hashtable_define_trivial_hash(_Tp)     \
  template<>                        \
    struct hash<_Tp> : public __hash_base<size_t, _Tp>  \
    {                                                   \
      size_t                                            \
      operator()(_Tp __val) const noexcept              \
      { return static_cast<size_t>(__val); }            \
    };

  /// Explicit specialization for bool.
  _Cxx_hashtable_define_trivial_hash(bool)

  /// Explicit specialization for char.
  _Cxx_hashtable_define_trivial_hash(char)

  /// ...

La spécialisation pour std::string est défini comme:

#ifndef _GLIBCXX_COMPATIBILITY_CXX0X
  /// std::hash specialization for string.
  template<>
    struct hash<string>
    : public __hash_base<size_t, string>
    {
      size_t
      operator()(const string& __s) const noexcept
      { return std::_Hash_impl::hash(__s.data(), __s.length()); }
    };

D'autres recherches nous mènent à:

struct _Hash_impl
{
  static size_t
  hash(const void* __ptr, size_t __clength,
       size_t __seed = static_cast<size_t>(0xc70f6907UL))
  { return _Hash_bytes(__ptr, __clength, __seed); }
  ...
};
...
// Hash function implementation for the nontrivial specialization.
// All of them are based on a primitive that hashes a pointer to a
// byte array. The actual hash algorithm is not guaranteed to stay
// the same from release to release -- it may be updated or tuned to
// improve hash quality or speed.
size_t
_Hash_bytes(const void* __ptr, size_t __len, size_t __seed);

_Hash_bytes est une fonction externe de libstdc++. Un peu plus de recherche m'a conduit à ce fichier , qui dit:

// This file defines Hash_bytes, a primitive used for defining hash
// functions. Based on public domain MurmurHashUnaligned2, by Austin
// Appleby.  http://murmurhash.googlepages.com/

Ainsi, l'algorithme de hachage par défaut que GCC utilise pour les chaînes est MurmurHashUnaligned2.

100
Avidan Borisov

Bien que les algorithmes de hachage dépendent du compilateur, je vais le présenter pour GCC C++ 11. @ Avidan Borisov a astucieusement découvert que l'algorithme de hachage GCC utilisé pour les chaînes est "MurmurHashUnaligned2", par Austin Appleby. J'ai fait quelques recherches et trouvé une copie miroir de GCC sur Github. Par conséquent:

Les fonctions de hachage GCC C++ 11 utilisées pour unordered_map (un modèle de table de hachage) et unordered_set (un modèle de jeu de hachage) semble être le suivant.

Code:

// Implementation of Murmur hash for 32-bit size_t.
size_t _Hash_bytes(const void* ptr, size_t len, size_t seed)
{
  const size_t m = 0x5bd1e995;
  size_t hash = seed ^ len;
  const char* buf = static_cast<const char*>(ptr);

  // Mix 4 bytes at a time into the hash.
  while (len >= 4)
  {
    size_t k = unaligned_load(buf);
    k *= m;
    k ^= k >> 24;
    k *= m;
    hash *= m;
    hash ^= k;
    buf += 4;
    len -= 4;
  }

  // Handle the last few bytes of the input array.
  switch (len)
  {
    case 3:
      hash ^= static_cast<unsigned char>(buf[2]) << 16;
      [[gnu::fallthrough]];
    case 2:
      hash ^= static_cast<unsigned char>(buf[1]) << 8;
      [[gnu::fallthrough]];
    case 1:
      hash ^= static_cast<unsigned char>(buf[0]);
      hash *= m;
  };

  // Do a few final mixes of the hash.
  hash ^= hash >> 13;
  hash *= m;
  hash ^= hash >> 15;
  return hash;
}

Pour des fonctions de hachage supplémentaires, notamment djb2, et les 2 versions des fonctions de hachage K&R (une apparemment terrible, une assez bonne), voir mon autre réponse ici: https://stackoverflow.com/a/45641002/4561887 .

4
Gabriel Staples