web-dev-qa-db-ja.com

ハッシュの衝突と文字列のパフォーマンスの点で最高のハッシュアルゴリズム

次の優先順位が(この順序で)ある場合、最適なハッシュアルゴリズムは何になりますか。

  1. 最小限のハッシュ衝突
  2. 性能

安全である必要はありません。基本的に、いくつかのオブジェクトのプロパティの組み合わせに基づいてインデックスを作成しようとしています。 すべてのプロパティは文字列です

C#実装への参照は歓迎されます。

50
dpan

「ベスト」という用語は忘れてください。誰がどのハッシュアルゴリズムを思いついても、ハッシュする必要のあるデータセットが非常に限られている場合を除き、平均で非常に良好に機能するすべてのアルゴリズムは、適切に(またはあなたの観点から) 「間違った」データ。

CPU時間を使いすぎずにハッシュをよりコリジョンフリーにする方法を考えるのに時間を浪費する代わりに、「コリジョンの問題を少なくする方法」を考え始めます。例えば。すべてのハッシュバケットが実際にテーブルであり、このテーブル内のすべての文字列(衝突した)がアルファベット順にソートされている場合、バイナリ検索(O(log n)のみ)を使用してバケットテーブル内を検索できます。 2番目のハッシュバケットごとに4回の衝突がある場合でも、コードのパフォーマンスはまあまあです(衝突のないテーブルに比べて少し遅くなりますが、それほど多くはありません)。ここでの大きな利点の1つは、テーブルが十分に大きく、ハッシュが単純すぎない場合、同じハッシュ値をもたらす2つの文字列は通常完全に異なって見えることです(したがって、バイナリ検索は平均で1文字または2文字で文字列の比較を停止できます) ;すべての比較を非常に高速にする)。

実際、バイナリ検索を使用してソートされたテーブル内を直接検索する方がハッシュよりも高速であることが判明する前に、私自身が状況を抱えていました!私のハッシュアルゴリズムは単純でしたが、値のハッシュにはかなりの時間がかかりました。パフォーマンステストにより、約700〜800を超えるエントリを取得した場合にのみ、ハッシュはバイナリ検索よりも実際に高速であることが示されました。ただし、とにかくテーブルが256エントリより大きくなることはなく、平均テーブルが10エントリを下回るため、ベンチマークでは、すべてのシステム、すべてのCPUでバイナリ検索が高速であることが明らかになりました。ここでは、通常、データの最初のバイトをすでに比較しているという事実は、次のbsearchの反復につながるのに十分でした(最初の1バイトから2バイトではデータが非常に異なっていたため)。

要約すると、平均してあまり多くの衝突を引き起こさず、かなり高速なハッシュアルゴリズムを採用します(非常に高速であれば、さらに衝突を受け入れます!)衝突が発生した後、パフォーマンスのペナルティを最小限に抑えます(そして、衝突が発生します!ハッシュスペースがデータスペース以上であり、一意のハッシュ値をすべての可能なデータセットにマップできない限り、衝突は発生します)。

33
Mecki

Nigel Campbell が示すように、「最高の」ハッシュ関数などはありません。これは、ハッシュするデータの特性と、暗号化品質のハッシュが必要かどうかに依存するためです。

とはいえ、ここにいくつかのポインタがあります。

  • ハッシュへの入力として使用している項目は単なる文字列のセットであるため、これらの個々の文字列のそれぞれのハッシュコードを単純に組み合わせることができます。これを行うために次の擬似コードが提案されているのを見ましたが、特定の分析については知りません。

    int hashCode = 0;
    
    foreach (string s in propertiesToHash) {
        hashCode = 31*hashCode + s.GetHashCode();
    }
    

    この記事 によると、System.Webには、以下を使用してハッシュコードを組み合わせる内部メソッドがあります。

    combinedHash = ((combinedHash << 5) + combinedHash) ^ nextObj.GetHashCode();
    

    ハッシュコードを単純にxorするコードも見ましたが、それは悪い考えのように思えます(ただし、これを裏付ける分析はありません)。他に何もなければ、同じ文字列が異なる順序でハッシュ化されると、衝突が発生します。

  • FNVを使用して効果を上げました: http://www.isthe.com/chongo/tech/comp/fnv/

  • Paul Hsiehにはまともな記事があります: http://www.azillionmonkeys.com/qed/hash.html

  • 1997年にドクタードブのジャーナルに最初に公開されたボブジェンキンスによる別の素敵な記事(リンクされた記事には更新があります): http://burtleburtle.net/bob/hash/doobs.html

17
Michael Burr

私はここで不自由になり、より正確な答えではなく、より理論的な反応を示しますが、その中に価値を取り入れてください。

まず、2つの明確な問題があります。

a。衝突確率b。ハッシュのパフォーマンス(つまり、時間、CPUサイクルなど)

2つの問題はやや緩和されています。それらは完全に相関しているわけではありません。

問題aは、ハッシュと結果のハッシュスペースの違いを扱います。 1KBファイル(1024バイト)ファイルをハッシュし、ハッシュに32バイトがある場合、次のようになります。

1,0907481356194159294629842447338e + 2466(つまり、2466個のゼロを持つ数字)入力ファイルの可能な組み合わせ

ハッシュ空間は

1,1579208923731619542357098500869e + 77(つまり、77個のゼロを持つ数字)

違いIS巨大。それらの間には2389個のゼロの違いがあります。衝突が発生します(衝突は、2つの異なる入力ファイルがまったく同じハッシュを持つ特別な場合です) ^ 2466ケースから10 ^ 77ケース。

衝突のリスクを最小限に抑える唯一の方法は、ハッシュスペースを拡大して、ハッシュを長くすることです。理想的には、ハッシュにはファイル長がありますが、これはなんらかの方法です。


2番目の問題はパフォーマンスです。これはハッシュのアルゴリズムのみを扱います。もちろん、より長いハッシュはより多くのCPUサイクルを必要としますが、よりスマートなアルゴリズムはそうではないかもしれません。この質問に対する明確なケースの回答はありません。それはあまりにも厳しいです。

ただし、異なるハッシュ実装をベンチマーク/測定し、これから事前結論を引き出すことができます。

幸運を ;)

8
Andrei Rînea

単一の最適なハッシュアルゴリズムはありません。既知の入力ドメインがある場合は、 gperf などの完全ハッシュジェネレーターを使用して、その特定の入力セットで100%のレートを得るハッシュアルゴリズムを生成できます。そうでなければ、この質問に対する「正しい」答えはありません。

JavaのStringクラスで使用される単純なhashCodeは、適切なアルゴリズムを示している可能性があります。

以下は「GNU Classpath」の実装です。 (ライセンス:GPL)

  /**
   * Computes the hashcode for this String. This is done with int arithmetic,
   * where ** represents exponentiation, by this formula:<br>
   * <code>s[0]*31**(n-1) + s[1]*31**(n-2) + ... + s[n-1]</code>.
   *
   * @return hashcode value of this String
   */
  public int hashCode()
  {
    if (cachedHashCode != 0)
      return cachedHashCode;

    // Compute the hash code using a local variable to be reentrant.
    int hashCode = 0;
    int limit = count + offset;
    for (int i = offset; i < limit; i++)
      hashCode = hashCode * 31 + value[i];
    return cachedHashCode = hashCode;
  }
3
activout.se

Knuthハッシュ関数 ここで説明 を使用して両方を取得できます。

ハッシュテーブルのサイズが2のべき乗であると仮定すると、非常に高速です(乗算1回、シフト1回、ビットアンド1回)。さらに重要なことは(あなたにとって)、衝突の最小化に優れていることです( この分析 を参照)。

他のいくつかの優れたアルゴリズムが ここ で説明されています。

2
Jason Cohen

「Murmurhash」は、パフォーマンスと衝突の両方で非常に優れています。

「softwareengineering.stackexchange」の言及されたスレッドにはいくつかのテストがあり、Murmurが勝ちました。

MurmurHash 2の独自のC#ポートを.NETに書き込み、466k英語の単語のリストでテストし、22の衝突を取得しました。

結果と実装はこちらです: https://github.com/jitbit/MurmurHash.net (免責事項、私はこのオープンソースプロジェクトに関わっています!)

1
Alex

自分で実装する簡単な方法を次に示します。 http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

投稿からの抜粋は次のとおりです。

大文字の英語の文字セットがある場合、文字セットの長さは26で、Aは数字0で、Bは数字1で、Cは数字2で、Zは数字まで25.これで、この文字セットの文字列を一意の数値にマップするたびに、バイナリ形式の場合と同じ変換を実行します

1
Abhishek Jain

Stackoverflowが大好き!この質問を読んで、ハッシュ関数をもう少し調べてみると、 Cuckoo Hash が見つかりました。

記事から:

ルックアップでは、ハッシュテーブル内の2つの場所のみを検査する必要があり、最悪の場合は一定の時間がかかります(Big O表記を参照)。これは他の多くのハッシュテーブルアルゴリズムとは対照的です。他の多くのハッシュテーブルアルゴリズムは、ルックアップを実行する時間に一定の最悪の場合の限界がない場合があります。

衝突とパフォーマンスの基準に適合すると思います。トレードオフは、このタイプのハッシュテーブルは49%しか満たすことができないということです。

1
Jason Z