web-dev-qa-db-ja.com

hash_mapを使用するときにstl文字列で使用するのに最適なハッシュアルゴリズムは何ですか?

VS2005の標準のハッシュ関数は、高性能なルックアップを達成しようとすると非常に遅いことがわかりました。ほとんどの衝突を無効にする高速で効率的なハッシュアルゴリズムの良い例は何ですか?

45
PiNoYBoY82

私は Paul Larson のMicrosoft Researchのハッシュテーブルの実装に取り​​組みました。彼はさまざまなデータセットの文字列ハッシュ関数の数を調査し、単純な101の乗算ループと加算ループが驚くほどうまく機能することを発見しました。

unsigned int
hash(
    const char* s,
    unsigned int seed = 0)
{
    unsigned int hash = seed;
    while (*s)
    {
        hash = hash * 101  +  *s++;
    }
    return hash;
}
63

私の古いコードから:

/* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
static const size_t InitialFNV = 2166136261U;
static const size_t FNVMultiple = 16777619;

/* Fowler / Noll / Vo (FNV) Hash */
size_t myhash(const string &s)
{
    size_t hash = InitialFNV;
    for(size_t i = 0; i < s.length(); i++)
    {
        hash = hash ^ (s[i]);       /* xor  the low 8 bits */
        hash = hash * FNVMultiple;  /* multiply by the magic number */
    }
    return hash;
}

これは速い。本当に速い。

19
Dark Shikari

Boostには boost :: hash ライブラリがあり、ほとんどの一般的なタイプに基本的なハッシュ関数を提供できます。

8
David Pierre

それは常にデータセットに依存します。

私は、文字列のCRC32を使用することで驚くほど良い結果を得ました。さまざまな入力セットで非常に良好に動作します。

多くの優れたCRC32実装は、ネット上で簡単に見つけることができます。

編集:ほとんど忘れてしまった:このページには、パフォーマンス番号とテストデータを含む素敵なハッシュ関数シュートアウトがあります:

http://smallcode.weblogs.us/ <-ページのさらに下に。

7

私はJenkinsハッシュを使用してブルームフィルターライブラリを記述しましたが、優れたパフォーマンスを発揮します。

詳細とコードはこちらから入手できます。 http://burtleburtle.net/bob/c/lookup3.c

これはPerlがハッシュ操作fwiwに使用するものです。

6
SquareCog

固定された一連の単語をハッシュする場合、最適なハッシュ関数は多くの場合 完全なハッシュ関数 です。ただし、通常は、ハッシュしようとしている単語のセットがコンパイル時にわかっている必要があります。 lexer (およびキーワードのトークンへの変換)でのキーワードの検出は、 gperf などのツールで生成された完全なハッシュ関数の一般的な使用法です。完全なハッシュを使用すると、hash_map単純な配列またはvector

固定の単語セットをハッシュしていない場合、明らかにこれは当てはまりません。

6
bk1e

Paul Larsonの小さなアルゴリズムがここに現れました http://www.strchr.com/hash_functions いくつかの条件でテストされたものの中で衝突が最も少ないものとして展開またはテーブル駆動型の場合、非常に高速です。

ラーソンは単純な101の乗算で、上記のループを追加します。

2
Josh S

Python 3.4には、 SipHash に基づく新しいハッシュアルゴリズムが含まれています。 PEP 456 は非常に有益です。

2

文字列ハッシュの古典的な提案の1つは、アキュムレータに素数を掛けるたびに、アスキー/ユニコード値をアキュムレータに追加して、文字を1つずつ進めることです。 (ハッシュ値のオーバーフローを許可)

  template <> struct myhash{};

  template <> struct myhash<string>
    {
    size_t operator()(string &to_hash) const
      {
      const char * in = to_hash.c_str();
      size_t out=0;
      while(NULL != *in)
        {
        out*= 53; //just a prime number
        out+= *in;
        ++in;
        }
      return out;
      }
    };

  hash_map<string, int, myhash<string> > my_hash_map;

データを捨てずにそれより速くなるのは難しいです。文字列がコンテンツ全体ではなく数文字だけで区別できることがわかっている場合は、より高速に処理できます。

値が頻繁に計算される場合、ハッシュ値を記憶するbasic_stringの新しいサブクラスを作成することにより、ハッシュ値のキャッシュを改善することができます。ただし、hash_mapは内部的にそれを行う必要があります。

2
Brian

ハッシュ関数をずっと下から から:

MurmurHash 少なくともゲーム開発者のサークルでは、「一般的なハッシュ関数」として非常に人気がありました。

それは良い選択ですが、一般的にもっと良くできるかどうかは後で見てみましょう。特に、「未知のバイト数になる」よりもデータについて詳しい場合は、自分でロールバックすることもできます(例えば、ウォンチュンの返信、または4バイトキーに特化したルーンの修正xxHash/Murmurを参照してください)等。)。データを知っている場合は、その知識を有効に使用できるかどうかを常に確認してください。

より多くの情報がなければ、汎用として MurmurHash をお勧めします 非暗号化ハッシュ関数 。 (プログラムの平均的な識別子のサイズの)小さな文字列の場合、非常にシンプルで有名な djb2 および [〜#〜] fnv [〜#〜] は非常に優れています。

ここ(データサイズ<10バイト)では、他のアルゴリズムのILPのスマートさがそれ自体を示さないことがわかり、FNVまたはdjb2の超シンプルさがパフォーマンスで勝っています。

djb2

unsigned long
hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

FNV-1

hash = FNV_offset_basis
for each byte_of_data to be hashed
     hash = hash × FNV_prime
     hash = hash XOR byte_of_data
return hash

FNV-1A

hash = FNV_offset_basis
for each byte_of_data to be hashed
     hash = hash XOR byte_of_data
     hash = hash × FNV_prime
return hash

セキュリティと可用性に関する注意

ハッシュ関数により、コードがサービス拒否攻撃に対して脆弱になる可能性があります。攻撃者がサーバーにあまりにも多くの衝突を処理させることができる場合、サーバーはリクエストに対処できない可能性があります。

MurmurHash などの一部のハッシュ関数は、サーバーソフトウェアが生成するハッシュを攻撃者が予測する能力を大幅に低下させるために提供できるシードを受け入れます。心に留めておきます。

1
philix

文字列が平均して1つのキャッシュラインよりも長いが、その長さ+プレフィックスが合理的に一意である場合は、長さ+最初の8/16文字だけにすることを検討してください。 (長さはstd :: stringオブジェクト自体に含まれているため、読みやすくなっています)

0
MSalters