web-dev-qa-db-ja.com

32ビット整数の低衝突率の高速文字列ハッシュアルゴリズム

クイック検索をしたい、無関係な名前の付いたものがたくさんあります。 「aardvark」は常に「aardvark」であるため、文字列をハッシュして整数を再利用すると、比較が高速化されます。名前のセット全体は不明です(および時間とともに変化します)。小さな(32または16)ビット値を生成し、衝突率が低い高速文字列ハッシュアルゴリズムとは何ですか?

C/C++に固有の最適化された実装をご覧ください。

64
Jason Citron

FNVバリアント のいずれかが要件を満たす必要があります。それらは高速で、かなり均等に分散された出力を生成します。

29
Nick Johnson

Murmur Hash はかなりいいです。

32
yrp

eternallyconfuzzled.com には Nice article もあります。

Jenkinsの文字列のOne-at-a-Timeハッシュは次のようになります。

#include <stdint.h>

uint32_t hash_string(const char * s)
{
    uint32_t hash = 0;

    for(; *s; ++s)
    {
        hash += *s;
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }

    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);

    return hash;
}
17
Christoph

固定文字列セットにはgperfを使用します。

文字列セットが変更された場合、1つのハッシュ関数を選択する必要があります。そのトピックは以前に議論されました:

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

17

ユースケースに応じてさらに良い別の解決策は、interned stringsです。これがシンボルの仕組みです。 LISPで。

インターンされた文字列は、値が実際の文字列バイトのアドレスである文字列オブジェクトです。そのため、グローバルテーブルをチェックインして、インターン化された文字列オブジェクトを作成します。文字列が含まれている場合、インターン化された文字列をその文字列のアドレスに初期化します。そうでない場合は、挿入してから、インターンされた文字列を初期化します。

これは、同じ文字列から作成された2つのインターンされた文字列が同じ値、つまりアドレスを持つことを意味します。したがって、Nがシステム内のインターンされた文字列の数である場合、特性は次のとおりです。

  • 遅い構築(ルックアップと場合によってはメモリ割り当てが必要)
  • 同時スレッドの場合、グローバルデータと同期が必要
  • CompareはO(1)です。これは、実際の文字列バイトではなくアドレスを比較しているためです(つまり、並べ替えは適切に機能しますが、アルファベット順ではありません)。

乾杯、

カール

8
Carl Seleborg

良いテーマに遅れることは決してなく、人々は私の調査結果に興味があると確信しています。

ハッシュ関数が必要でした。この投稿を読んで、ここで与えられたリンクについて少し調査した後、ダニエル・J・バーンスタインのアルゴリズムのこのバリエーションを思いつきました。

unsigned long djb_hashl(const char *clave)
{
    unsigned long c,i,h;

    for(i=h=0;clave[i];i++)
    {
        c = toupper(clave[i]);
        h = ((h << 5) + h) ^ c;
    }
    return h;
}
</ code>

このバリエーションでは、大文字と小文字を区別せずに文字列をハッシュします。これは、ユーザーのログイン資格情報をハッシュするという私のニーズに合っています。 「clave」はスペイン語で「キー」です。スペイン語は残念ですが、私の母国語とプログラムはそれに書かれています。

さて、「test_aaaa」から「test_zzzz」までのユーザー名を生成するプログラムを作成しました。文字列を長くするために、このリストにランダムなドメインを追加しました。「cloud-nueve.com」、「yahoo.com」 '、' gmail.com 'および' hotmail.com '。したがって、それらはそれぞれ次のようになります。

 
 test_aaaa @ cloud-nueve.com、test_aaab @ yahoo.com、
 test_aaac @ gmail.com、test_aaad @ hotmail.comなど。
 

テストの出力は次のとおりです-「コリジョンエントリXXX y XXX」は「XXXとXXXの衝突」を意味します。 「palabras」は「words」を意味し、「Total」は両方の言語で同じです。

 
 Buscando Colisiones ... 
 Collision entre '[email protected]' y '[email protected]'(1DB903B7)
 Colision entre ' [email protected] 'y' [email protected] '(2F5BC088)
 Collision entre' [email protected] 'y' [email protected] '(51FD09CC)
 Colision entre '[email protected]' y '[email protected]'(52F5480E)
 Colision entre '[email protected]' y '[email protected]'(74FF72E2)
 Collision entre '[email protected]' y '[email protected]'(7FD70008)
 Colision entre '[email protected]' y '[email protected]'(9BD351C4) 
 Collision entre '[email protected]' y '[email protected]'(A86953E1)
 Collision entre '[email protected]' y '[email protected]'( BA6B0718)
 Collision entre '[email protected]' y '[email protected]'(D0523F88)
 Colision entre '[email protected]' y '[email protected]'( DEE0 8108)
コリジョンの合計:11 
パラブラスの合計:456976 
 

456,976のうち11回のコリジョン(コース全体で32ビットをテーブルの長さとして使用)は、悪くありません。

「test_aaaaa」から「test_zzzzz」までの5文字を​​使用してプログラムを実行すると、実際にはテーブルを構築するメモリが不足します。以下は出力です。 「挿入子XXXX(挿入アダスXXX)の干し草のメモリはありません」は、「XXX(XXXを挿入)を挿入するためのメモリが残っていない」ことを意味します。基本的に、その時点でmalloc()は失敗しました。

[。 .451 'colision'文字列... 
 
合計de Colisiones:451 
 Total de Palabras:2097701 
 

これは、2,097,701文字列で451回の衝突を意味します。どの場合でも、コードごとに2回以上の衝突があったことに注意してください。必要なのは、ログインIDをインデックス作成用の40ビットの一意のIDに変換することなので、これは私にとって素晴らしいハッシュであることを確認しています。したがって、これを使用してログイン資格情報を32ビットハッシュに変換し、余分な8ビットを使用してコードごとに最大255の衝突を処理します。

これが誰かに役立つことを願っています。

編集:

テストボックスがAIXのように、LDR_CNTRL = MAXDATA = 0x20000000を使用して実行してメモリを増やし、実行時間を長くします。結果は次のとおりです。

Buscando Colisiones ... Total de Colisiones:2908 Total de Palabras:5366384

5,366,384回試行した後は2908です!!

非常に重要:-maix64でプログラムをコンパイルするため(符号なしlongは64ビット)、衝突の数はすべての場合で0です!!!

4
Antonio Morales

Boostライブラリ? を使用しないのはなぜですか?ハッシュ関数の使い方は簡単で、Boostのほとんどの機能はまもなくC++標準の一部になります。その一部はすでにあります。

Boostハッシュは次のように簡単です

#include <boost/functional/hash.hpp>

int main()
{
    boost::hash<std::string> string_hash;

    std::size_t h = string_hash("Hash me");
}

boost.org でブーストを見つけることができます

4
Bernard Igiri

Bob Jenkinsには多くのハッシュ関数が用意されています 、これらはすべて高速で衝突率が低いです。

3
user7116

GNU gperf をご覧ください。

3
Rob Wells

Hsieh ハッシュ関数は非常に優れており、Cの一般的なハッシュ関数として、いくつかのベンチマーク/比較があります。目的に応じて(完全には明らかではありませんが)、 cdb 代わりに。

3
James Antill

Reflectorを使用してString.GetHashCode()メソッドで.NETが使用するものを確認できます。

マイクロソフトがこれを最適化するのにかなりの時間を費やしたと推測するのは危険です。それらはすべてのMSDNドキュメントにも印刷されており、常に変更される可能性があります。だから明らかにそれは彼らの「性能調整レーダー」にあります;-)

C++に移植するのもかなり簡単だと思いました。

2
nbevans

これにはいくつかの良い議論があります 前の質問

そして、ハッシュ関数を選ぶ方法の素敵な概要と、いくつかの一般的なものの分布に関する統計 here

2
AShelly

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

投稿からの抜粋:

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

0
Abhishek Jain