VS2005の標準のハッシュ関数は、高性能なルックアップを達成しようとすると非常に遅いことがわかりました。ほとんどの衝突を無効にする高速で効率的なハッシュアルゴリズムの良い例は何ですか?
私は 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;
}
私の古いコードから:
/* 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;
}
これは速い。本当に速い。
Boostには boost :: hash ライブラリがあり、ほとんどの一般的なタイプに基本的なハッシュ関数を提供できます。
それは常にデータセットに依存します。
私は、文字列のCRC32を使用することで驚くほど良い結果を得ました。さまざまな入力セットで非常に良好に動作します。
多くの優れたCRC32実装は、ネット上で簡単に見つけることができます。
編集:ほとんど忘れてしまった:このページには、パフォーマンス番号とテストデータを含む素敵なハッシュ関数シュートアウトがあります:
http://smallcode.weblogs.us/ <-ページのさらに下に。
私はJenkinsハッシュを使用してブルームフィルターライブラリを記述しましたが、優れたパフォーマンスを発揮します。
詳細とコードはこちらから入手できます。 http://burtleburtle.net/bob/c/lookup3.c
これはPerlがハッシュ操作fwiwに使用するものです。
Paul Larsonの小さなアルゴリズムがここに現れました http://www.strchr.com/hash_functions いくつかの条件でテストされたものの中で衝突が最も少ないものとして展開またはテーブル駆動型の場合、非常に高速です。
ラーソンは単純な101の乗算で、上記のループを追加します。
文字列ハッシュの古典的な提案の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は内部的にそれを行う必要があります。
ハッシュ関数をずっと下から から:
MurmurHash 少なくともゲーム開発者のサークルでは、「一般的なハッシュ関数」として非常に人気がありました。
それは良い選択ですが、一般的にもっと良くできるかどうかは後で見てみましょう。特に、「未知のバイト数になる」よりもデータについて詳しい場合は、自分でロールバックすることもできます(例えば、ウォンチュンの返信、または4バイトキーに特化したルーンの修正xxHash/Murmurを参照してください)等。)。データを知っている場合は、その知識を有効に使用できるかどうかを常に確認してください。
より多くの情報がなければ、汎用として MurmurHash をお勧めします 非暗号化ハッシュ関数 。 (プログラムの平均的な識別子のサイズの)小さな文字列の場合、非常にシンプルで有名な djb2 および [〜#〜] fnv [〜#〜] は非常に優れています。
ここ(データサイズ<10バイト)では、他のアルゴリズムのILPのスマートさがそれ自体を示さないことがわかり、FNVまたは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;
}
hash = FNV_offset_basis
for each byte_of_data to be hashed
hash = hash × FNV_prime
hash = hash XOR byte_of_data
return hash
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つのキャッシュラインよりも長いが、その長さ+プレフィックスが合理的に一意である場合は、長さ+最初の8/16文字だけにすることを検討してください。 (長さはstd :: stringオブジェクト自体に含まれているため、読みやすくなっています)