一意性と速度に最適なハッシュアルゴリズムはどれですか。例(良い)は、ハッシュ辞書を含みます。
SHA-256 のようなものがあることは知っていますが、これらのアルゴリズムはで設計されていますsecureこれは通常、より少ないuniqueであるアルゴリズムよりも遅いことを意味します。高速になるように設計されたハッシュアルゴリズムが必要ですが、衝突を避けるためにかなりユニークなままです。
いくつかの異なるアルゴリズムをテストし、速度と衝突の数を測定しました。
私は3つの異なるキーセットを使用しました。
"1"
から"216553"
までの数字(郵便番号を考えてみてください 貧弱なハッシュがmsn.comをダウンさせた方法 ???? archive)コーパスごとに、衝突の数とハッシュに費やされた平均時間が記録されました。
私がテストした:
+
ではなくxor
を使用したバリアント)各結果には、平均ハッシュ時間と衝突の数が含まれています
Hash Lowercase Random UUID Numbers
============= ============= =========== ==============
Murmur 145 ns 259 ns 92 ns
6 collis 5 collis 0 collis
FNV-1a 152 ns 504 ns 86 ns
4 collis 4 collis 0 collis
FNV-1 184 ns 730 ns 92 ns
1 collis 5 collis 0 collis▪
DBJ2a 158 ns 443 ns 91 ns
5 collis 6 collis 0 collis▪▪▪
DJB2 156 ns 437 ns 93 ns
7 collis 6 collis 0 collis▪▪▪
SDBM 148 ns 484 ns 90 ns
4 collis 6 collis 0 collis**
SuperFastHash 164 ns 344 ns 118 ns
85 collis 4 collis 18742 collis
CRC32 250 ns 946 ns 130 ns
2 collis 0 collis 0 collis
LoseLose 338 ns - -
215178 collis
メモ:
はい。ハッシュの衝突が実際に発生するかどうかを確認するためのテストプログラムの作成を開始しました。これは単なる理論上の構成ではありません。彼らは確かに起こります:
FNV-1コリジョン
creamwove
はquists
と衝突しますFNV-1aの衝突
costarring
はliquid
と衝突しますdeclinate
はmacallums
と衝突しますaltarage
はzinke
と衝突しますaltarages
はzinkes
と衝突しますMurmur2の衝突
cataract
はperiti
と衝突しますroquette
はskivie
と衝突しますshawl
はstormbound
と衝突しますdowlases
はtramontane
と衝突しますcricketings
はtwanger
と衝突しますlongans
はwhigs
と衝突しますDJB2衝突
hetairas
はmentioner
と衝突しますheliotropes
はneurospora
と衝突しますdepravement
はserafins
と衝突しますstylist
はsubgenera
と衝突しますjoyful
はsynaphea
と衝突しますredescribed
はurites
と衝突しますdram
はvivency
と衝突しますDJB2aコリジョン
haggadot
はloathsomenesses
と衝突しますadorablenesses
はrentability
と衝突しますplaywright
はsnush
と衝突しますplaywrighting
はsnushing
と衝突しますtreponematoses
はwaterbeds
と衝突しますCRC32コリジョン
codding
はgnu
と衝突しますexhibiters
はschlager
と衝突しますSuperFastHashコリジョン
dahabiah
はdrapability
と衝突しますencharm
はenclave
と衝突しますgrahams
はgramary
と衝突しますnight
はvigil
と衝突しますnights
はvigils
と衝突しますfinks
はvinic
と衝突します他の主観的な尺度は、ハッシュのランダムな分布です。結果のHashTableをマッピングすると、データがどの程度均等に分散されているかがわかります。テーブルを線形にマッピングすると、すべてのハッシュ関数が適切な分布を示します。
または Hilbert Map ( XKCDは常に関連しています )として:
数字の文字列("1"
、"2"
、...、"216553"
)をハッシュする場合(例: 郵便番号 )を除いて、ほとんどのパターンが出現し始めますハッシュアルゴリズムの:
[〜#〜] sdbm [〜#〜]:
DJB2a:
FNV-1:
FNV-1aを除くすべて、それでも私にはかなりランダムに見えます:
実際、Murmur2はNumbers
の方がFNV-1a
よりもランダム性が優れているようです:
FNV-1a
"number"マップを見ると、think微妙な垂直パターンが見えます。 Murmurでは、パターンはまったく見えません。どう思いますか?
表の余分な*
は、ランダム性がいかに悪いかを示しています。 FNV-1a
が最高で、DJB2x
が最低です:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
私はもともと、このプログラムを書いて、衝突について心配することをする必要があるかどうかを判断しました。
そして、ハッシュ関数が十分にランダムであることを確認するようになりました。
FNV1ハッシュには、32、64、128、256、512、1024ビットハッシュを返すバリアントがあります。
FNV-1aアルゴリズム は次のとおりです。
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
定数FNV_offset_basis
およびFNV_prime
は、必要な戻りハッシュサイズによって異なります。
Hash Size
===========
32-bit
prime: 2^24 + 2^8 + 0x93 = 16777619
offset: 2166136261
64-bit
prime: 2^40 + 2^8 + 0xb3 = 1099511628211
offset: 14695981039346656037
128-bit
prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
offset: 144066263297769815596495629667062367629
256-bit
prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915
詳細は FNVのメインページ を参照してください。
私の結果はすべて32ビットバリアントでのものです。
いいえ。FNV-1aの方がずっと優れています。英語の単語コーパスを使用すると、FNV-1aとの衝突がさらに発生しました。
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
次に、小文字と大文字を比較します。
Hash lowercase Word Collisions UPPERCASE Word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
この場合、FNV-1aはFN-1よりも "400%"悪いわけではなく、20%だけ悪くなっています。
さらに重要なことは、衝突に関しては2つのクラスのアルゴリズムがあるということです。
そして、ハッシュがどのように均等に分散されるかがあります:
更新
つぶやく? 確かに、なぜそうではないのか
更新
@whatshisnameは、CRC32がどのように機能するか疑問に思い、表に数値を追加しました。
CRC32はかなり良いです。衝突はほとんどありませんが、速度は遅く、1kルックアップテーブルのオーバーヘッドです。
CRC配布に関するすべての誤ったものを切り取る-私の悪い
今日まで、私はFNV-1aをデファクトハッシュテーブルハッシュアルゴリズムとして使用していました。しかし、今はMurmur2に切り替えています。
そして、私は本当に、本当にSuperFastHash
アルゴリズムに問題があることを望んでいます ;それは、それがそうであるように人気があるにはあまりにも悪いです。
更新:From GoogleのMurmurHash3ホームページ :
(1)-SuperFastHashのコリジョンプロパティは非常に貧弱ですが、他の場所で文書化されています。
だから私だけじゃないかな。
更新:Murmur
が他より速い理由に気付きました。 MurmurHash2は一度に4バイトで動作します。ほとんどのアルゴリズムはbyte by byteです:
for each octet in Key
AddTheOctetToTheHash
これは、キーが長くなると、Murmurが光る機会を得ることを意味します。
更新
Raymond Chenによるタイムリーな投稿は、 "random"GUIDがそのランダム性のために使用されることを意図していないという事実を繰り返し述べています。それら、またはそれらのサブセットは、ハッシュキーとして不適切です。
バージョン4 GUIDアルゴリズムは、アルゴリズムが乱数ジェネレーターの品質を指定していないため、予測不可能であることが保証されていません。 GUIDが示唆する主要な研究が含まれています ジェネレーターは暗号的に強力ではないため、乱数ジェネレーターの状態の知識に基づいて将来および以前のGUIDを予測できることを示しています。
ランダムメスは衝突回避と同じではありません。これが、「ランダムな」GUIDのサブセットを取得することによって独自の「ハッシュ」アルゴリズムを発明しようとするのが誤りであろう理由です。
int HashKeyFromGuid(Guid type4uuid)
{
//A "4" is put somewhere in the GUID.
//I can't remember exactly where, but it doesn't matter for
//the illustrative purposes of this pseudocode
int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
Assert(guidVersion == 4);
return (int)GetFirstFourBytesOfGuid(type4uuid);
}
注:繰り返しますが、 "random GUID"を引用符で囲みます。 GUIDの。より正確な説明はType 4 UUID
です。しかし、タイプ4、またはタイプ1、3、および5が何であるかは誰にもわかりません。したがって、それらを「ランダムな」GUIDと呼ぶ方が簡単です。
不変の辞書からハッシュマップを作成する場合は、完全なハッシュを検討することをお勧めします https://en.wikipedia.org/wiki/Perfect_hash_function -ハッシュ関数の構築中にハッシュテーブルを使用すると、特定のデータセットについて、衝突がないことを保証できます。
ここ はハッシュ関数のリストですが、短いバージョンは次のとおりです。
良いハッシュ関数を使いたいだけで、待つことができない場合は、
djb2
は、私が知っている最高の文字列ハッシュ関数の1つです。キーとテーブルサイズのさまざまなセットで優れた分散と速度を実現
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;
}
CityHash by Googleは、探しているアルゴリズムです。暗号化には適していませんが、一意のハッシュを生成するのには適しています。
詳細については ブログ と コードはこちらから入手可能 を参照してください。
CityHashはC++で書かれています。 プレーンCポート もあります。
すべてのCityHash関数は64ビットプロセッサ用に調整されています。つまり、32ビットコードで実行されます(SSE4.2を使用する新しいものを除く)。彼らはしかし、あまり速くはありません。 Murmurなどを32ビットコードで使用することもできます。
ファイルをハッシュするときに、さまざまなハッシュアルゴリズムの短い速度の比較をプロットしました。
すべてのファイルはtmpfsに保存されているため、個々のプロットは読み取り方法がわずかに異なるだけで、ここでは無視できます。したがって、疑問に思っているのであれば、ベンチマークはIO制約ではありませんでした。
アルゴリズムは次のとおりです:SpookyHash, CityHash, Murmur3, MD5, SHA{1,256,512}
。
結論:
CRC
命令を使用して、CPUにはないため、CPUの方が高速である可能性があります。私の場合、SpookyHashは常にCityHashよりも少し前でした。プロットに使用されるソース:
SHAアルゴリズム(SHA-256を含む)は設計高速です。
実際、速度が問題になる場合があります。特に、パスワードから派生したトークンを格納するための一般的な手法は、標準の高速ハッシュアルゴリズムを10,000回実行することです(...のハッシュのハッシュのハッシュを保存する)。
#!/usr/bin/env Ruby
require 'securerandom'
require 'digest'
require 'benchmark'
def run_random_digest(digest, count)
v = SecureRandom.random_bytes(digest.block_length)
count.times { v = digest.digest(v) }
v
end
Benchmark.bmbm do |x|
x.report { run_random_digest(Digest::SHA256.new, 1_000_000) }
end
出力:
Rehearsal ------------------------------------
1.480000 0.000000 1.480000 ( 1.391229)
--------------------------- total: 1.480000sec
user system total real
1.400000 0.000000 1.400000 ( 1.382016)
私はSHA-256のようなものがあることを知っていますが、これらのアルゴリズムは設計されたである安全です。これは通常、より少ないunique。
暗号化ハッシュ関数がより一意であるという仮定は誤りであり、実際には、実際には逆方向であることが多いことが示されています。実は:
つまり、非暗号化ハッシュ関数は、「適切な」データセット、つまり設計されたデータセットの暗号化ハッシュ関数よりも衝突が少ないである可能性があります。
実際に、Ian Boydの回答のデータと少しの計算で Birthday problem というデータを使用してこれを実証できます。セットからランダムにn
整数を選択した場合に予想される衝突ペア数の数式[1, d]
はこれです(Wikipediaから取得):
n - d + d * ((d - 1) / d)^n
n
= 216,553およびd
= 2 ^ 32を接続すると、約5.5予想される衝突が得られます。 Ianのテストは主にその近傍の結果を示していますが、劇的な例外が1つあります。ほとんどの関数は、連続数テストでゼロ衝突を得ました。ランダムに216,553個の32ビット数を選択し、衝突をゼロにする確率は、約0.43%です。そして、これは1つの関数に限られます。ここでは、衝突のない5つの異なるハッシュ関数ファミリがあります。
つまり、ここで確認しているのは、Ianがテストしたハッシュがfavorablyと連続した数値のデータセットと相互作用しているということです。つまり、最小の異なる入力を分散しています。理想的な暗号ハッシュ関数よりも広く。 (側注:これは、数値データセットでFNV-1aとMurmurHash2が彼に「ランダムに見える」というIanのグラフィカル評価は、彼自身のデータから反論できることを意味します。両方ハッシュ関数、驚くほどランダムではありません!)
これはハッシュ関数の多くの用途にとって望ましい動作であるため、これは驚きではありません。たとえば、ハッシュテーブルのキーはよく似ています。 Ianの回答には次のような記述があります MSNがかつて郵便番号のハッシュテーブルで抱えていた問題 。これは、likely入力の衝突回避がランダムのような動作よりも優れている用途です。
ここでの有益なもう1つの比較は、CRCと暗号化ハッシュ関数の設計目標の対比です。
したがって、CRCの場合も、最小限の異なる入力でランダムよりも衝突が少ないのはgoodです。暗号ハッシュでは、これはノーノーです!
SipHash を使用します。 多くの望ましい特性があります:
Fast。最適化された実装は、バイトあたり約1サイクルかかります。
安全です。SipHashは強力なPRF(疑似ランダム関数)です。これは、ランダムな関数と区別がつかないことを意味します(128ビットの秘密鍵を知らない限り)。したがって:
衝突によりハッシュテーブルプローブが線形時間になることを心配する必要はありません。 SipHashを使用すると、入力に関係なく、平均で平均ケースのパフォーマンスが得られることを知っています。
ハッシュベースのサービス拒否攻撃に対する耐性。
SipHash(特に128ビット出力のバージョン)をMAC(メッセージ認証コード)として使用できます。メッセージとSipHashタグを受け取り、そのタグが秘密鍵を使用してSipHashを実行した場合と同じである場合、ハッシュを作成した人が秘密鍵も所有しており、メッセージもハッシュはそれ以来変更されています。
ハッシュするデータによって異なります。一部のハッシュは、テキストなどの特定のデータでより適切に機能します。一部のハッシュアルゴリズムは、特定のデータに適するように特別に設計されています。
Paul Hsiehがかつて作った 高速ハッシュ 。彼はソースコードと説明をリストしています。しかし、それはすでに打ち負かされていました。 :)
Javaは this 単純な乗加算アルゴリズムを使用します。
Stringオブジェクトのハッシュコードは次のように計算されます
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
int演算を使用して、ここで
s[i]
は文字列のi番目の文字、n
は文字列の長さ、^
は、べき乗を示します。 (空の文字列のハッシュ値はゼロです。)
おそらくはるかに優れたものが存在しますが、これはかなり広く普及しており、速度と一意性の間の適切なトレードオフのようです。
まず、なぜ独自のハッシュを実装する必要があるのですか?ほとんどのタスクでは、利用可能な実装があることを前提として、標準ライブラリのデータ構造で良い結果が得られるはずです(自分の教育のためにこれを行っているだけの場合を除く)。
実際のハッシュアルゴリズムに関する限り、私の個人的なお気に入りはFNVです。 1
以下は、Cでの32ビットバージョンの実装例です。
unsigned long int FNV_hash(void* dataToHash, unsigned long int length)
{
unsigned char* p = (unsigned char *) dataToHash;
unsigned long int h = 2166136261UL;
unsigned long int i;
for(i = 0; i < length; i++)
h = (h * 16777619) ^ p[i] ;
return h;
}