[翻訳]は、世界最速のint-int型のマップを達成します

ソースは最近Map実装をたくさん読んで、比較可能な達成するために、様々なタイプの性能を比較評価を追跡し、ちょうど私の要求を満たすために、この記事を見つけました。(дが···しかし、」○(時間実行されて)テクノ)に評価プログラムのRANを利用して、このような良い記事を翻訳する誰を探していないためにビットを検索し、コンテンツが比較の基準であってもよいが、これ追求精神の卓越性は、私が以前の読み取りCSAppという感じだと思うので、私を感染させ、それを翻訳することを決めました。一方、著者ミハイルボロンツォフ他の記事では、Javaのパフォーマンス上のコンテンツやJavaチューニングのヒントがたくさんあり、また非常に良いです。

オリジナル住所:http://java-performance.info/implementing-world-fastest-java-int-to-int-hash-map/

私は感謝したいセバスヴィーニャローマLeventovを、私はハッシュテーブルを実現するための知恵を共有しています。アイデアのいくつかはまた、クリスカスペルスキーの「:効果的なメモリ使用量の最適化コード」から来実装します 。

この資料では、近代的なハッシュテーブルの実装の広い範囲を楽しむために一歩一歩になります。最後に、あなたはおそらく、Javaのintツーint型のハッシュテーブルを書いて最速タイムを取得します。

送付方法を開きます

最近のほとんどのハッシュテーブルは、オープンアドレス法に基づいています。これは何を意味するのでしょうか?あなたのハッシュテーブルはキーの配列に基づいて行われます(値は今のためにそれらを忘れて、配列内の適切な位置に配置されます)。各動作中には、最初のキー配列内の特定のキーを見つけなければなりません。それでは、どのようにそれを達成していますか?

まず、あなたは開始位置を必要とします。任意のハッシュ関数をキーにマッピングすることができる - 区間[0、長さ1]です。典型的には、キーマッピングhashCodeメソッド整数によって、単純なハッシュ関数によって、例えばMath.abs(key.hashCode()%のArray.lengthと)(%計算が負であることができる覚えています)。

ご存知のように、多数の鍵は、いくつかの葛藤を生み出す小さなハッシュテーブルにマッピングされている(私たちは、ハッシュ衝突と呼ばれます)。同一の初期位置を見つけるために異なるキーが、競合が他の機能によって解決することができ、例えば、この機能を必要とする(prevIdx + 1)%のArray.lengthとは、アレイ全体の容量をカバーすることができなければなりません。アレイShihai素数関数の長さは、素数をオフセットすることによって達成することができる場合。

空のセルとセルが削除されました

理論的には、あなたは、今のあなた自身のハッシュテーブルを実装することができます。しかし、実際には、あなたは(あなたは、ユニットのマークを削除避けることができますが、removeメソッドでは、いくつかの余分な作業を行う場合は、あなたが見てとることができ、空のセルと削除ユニットを区別する必要がFastUtilを達成しました)。削除されたユニットは、また、「墓石」と呼ばれています。

あなたの鍵は、既存のキーを削除した場合、あなたはそれがステータスを「削除」フラグに必要な、空のセルに直接充填することができます。

次の例を見てください:

オープンアドレス法の例

以下の機能と再プロービング機能を持つint型初期ハッシュテーブル:

1 
2
初期= Math.abs(キー%のArray.lengthと)。
nextIdx =(prevIdx + 1)%のArray.lengthと。

ハッシュテーブルは、キー2、3及び4を有しているが、3は削除され、大量に利用Rので、

私たちは、次のキーを見つける方法を見てみましょう。

キー 説明
2 他は検索しないように初期点インデックス機能部2,2のキー一致検索、
3 最初のファンクションポイントインデックスユニット3、、「削除」あなたが探しているまで、私たちは検出機能に適用する必要があるので、キーを探し続けたり、空のユニットとして、このユニットがマークされている:インデックスユニット4は、インデックスが一致していません5我々は検索を停止してユニットは、空のセルである - ルックアップが失敗しました

ここでは、キー= 10を追加する方法を示しています。初期=キー%のArray.lengthと= 10%9 = 1、我々はそれを使用することはできませんので、インデックス1ユニットは、別のキーによって占有されています。再プローブインデックス2、インデックス部3が削除されるまで、それを再利用することができ、キー= 10充填同じです。

削除されたユニットを取り外し

あなたが削除した細胞が、ハッシュテーブルに残りたい場合は、多くの場合、あなたはハッシュマップ(N2)複雑O.に格下げされる可能性があります 効率的なハッシュテーブルは、細胞を除去するために、いくつかの方法で削除されました。アイドル状態または使用:他のすべての方法は、単位状態の二種類を区別することが必要です。同時に、それは通常はるかに少ないプット方法よりも、Removeメソッドを使用していません。この記事ではFastUtilクリーンアップ・ロジックを使用します。

ハッシュスクランブル

上記の関数の最初の使用、我々は継続的なキーを配置する必要がある場合、それは長いのルックアップチェーンの原因となります。これを避けるために、我々は、例えば、そのビットを乱し、スクランブルハッシュする必要があります。

1 
2
3
4
5
6
プライベート 静的 最終 int型 INT_PHI = 0x9E3779B9パブリック静的INT 最終int型 X) { 最終int型 H = X * INT_PHI。戻り H ^(H >> 16)。}





このように、キーを連続連続アレイユニットに防止することがないので、今度は、平均長ことを見つけるために保持しました。ランダムキーのために、彼らはより良い配布されます。

今、あなたは完全に自分のハッシュテーブルを実現している、と私たちは紙に次のいくつかのセクションが、それを達成すること。

バージョン1:基本のint-int型のハッシュテーブル

私たちは、最初に(十分な最適化スペースがある)十分な、単純なハッシュテーブルを実現します。この実装は非常にTIntIntHashMapのトローブ3.0(私はそのソースコードをコピーしていない)したいと思います。

「使用済み」フラグを配置するために使用するブール[]が存在する場所の値に] [場所キー、intにINT []:これは、3つの配列を使用します。我々は彼らに最初のスペースが割り当てられます(サイズは/ FILLFACTOR + 1することができます)。そして、初期関数とプローブ機能は次のとおりです。

1 
2
初期= Math.abs(Tools.phiMix(キー)%のArray.lengthと)。
nextIdx =(prevIdx + 1)%のArray.lengthと。

あなたはこの記事の最後にソースコードや他のハッシュテーブルのハッシュテーブルを見ることができます。

私はコントラストになる前の記事、私たちはそれとKolobokeコントラストを参照してください。結果を試験物品のすべてがランダムなキーのコレクション、ハッシュテーブルのフィルファクタであるすべて0.75です。

地図のサイズ: 10.000 100.000 1.000.000 10.000.000 100.000.000
KolobokeMap 1867 2471 3129 7546 11191
IntIntMap1 2768 3671 6105 12313 16073

すばらしいです!最初の試みは、2回だけ遅くKolobokeより最適化されていません。

バージョン2:%避け高価な操作 - アレイ容量は、2の累乗になります

多くの人々は、その整数の除算の剰余演算は、もはやそれほど遅いと信じていない - ので、新しいCPUをより良く、より速く。しかし、これは間違っています。すべての整数の除算の性能が過酷なコードで表示されてはならない、非常に遅いスロー。

モジュロ演算子%を使用して、ハッシュ・テーブルの現在のバージョンは、配列の長さは、我々はそれを避けるために2の累乗になることができます。次のとおりです。

1 
2
初期= Tools.phiMix(キー)&(array.length- 1)。
nextIdx =(prevIdx + 1)&(array.length- 1)。

Array.lengthと - 1は、ドメイン内のインスタンスをキャッシュすると、それをマスクとして、なぜそれを使用することができますか?なぜならもしK = 2 ^ Nは、X%K == X&(K - 1)。・計算結果を演算処理まで非負、さらに速度を用いて作製することができます。

すべてが高性能なハッシュテーブルの上に、この最適化を頼ることを覚えておいてください。

いくつかの比較結果で見てみましょう:

[翻訳]世界最速のint-int型のマップ実現 に大きな列rを>
地図のサイズ: 10.000 100.000 1.000.000 10.000.000 100.000.000
KolobokeMap 1867 2471 3129 7546 11191
IntIntMap1 2768 3671 6105 12313 16073
Intintnap2 2254 2767 4869 10543 16724

この最適化は、私たちに飛躍することができます!その後には長い道のりがあります。

バージョン3:M-使用される配列を取り除きます

三つの異なるアレイを有するハッシュテーブルの前のバージョンでは、データを格納します。この手段は、三つの異なるメモリ領域を取得する必要がありそうなCPUのキャッシュ・ミスを引き起こすこと。高性能のコードでは、キャッシュ・ミスの数、我々はM-使用方法賢くアレイと識別単位で削除することができ、最も直感的な最適化を最小限に抑える必要があります。

問題は、我々は我々が使用しますので、int型ツーint型のハッシュテーブルを実装しているということである任意のキーとしてint型を(いくつかのキーNが予約されている場合とでどのように悲しい使用することはできません)。この手段は、我々は正しい、追加のストレージを特定する必要がありますか?はい、しかしキーは、我々はO(n)はO(1)の代わりに使用することができますです!

アイデアは、空のセルを識別するための特別なキーを選択することです。次のように2つの戦略は、(私が最初のものを使用します)があります。

1スペースの追加のメモリ空間部識別値を使用します。このキーが使用されているかどうかを識別するためのフラグを必要とする、各ハッシュ・テーブル・メソッドの先頭には、チェックすることと異なる論理のアクションを行う必要があります。
ランダム空のセルのキーを選択します。あなたはハッシュテーブルに空のセルのキーを挿入しようとしている場合は、新しいランダム空のセルのキーを選択し、キーをカバーする前に、すべての空のセルのハッシュテーブルに現れず、決してれます。Kolobokeこれが唯一の戦略です。

ちなみに、空のセルの値の配列は、キーは非常に単純されるオブジェクトを選択した場合:

1
プライベート 静的 最終オブジェクトFREE_KEY = 新しいオブジェクト();

キーは、アクセスの他のタイプではありません、それはおそらく、あなたのハッシュテーブルに渡されることはありません。いくつかの賢明な男の質問の種類を反映して言った、キーのハッシュテーブル覚えているかもしれませんがイコールとハッシュコードをオーバーライドする必要がありますが、プライベートオブジェクトはそうではありません。

Iは、上述したように、我々は、ハードコードされた(0、最も便利なの定数比較)の使用を実装し、一例ドメイン内の彼の対応する値が格納されます。

1 
2
3
4
5
6
7
8
9
10
11
プライベート 静的 最終 int型の FREE_KEY = 0 ;


プライベート int型 [] m_keys。
/ **値* /
プライベート int型 [] m_values。/ **我々は、マップ内の「無料」キーを持っていますか?* / プライベートブール m_hasFreeKey。/ **「自由」キーの値* / プライベートint型 m_freeValue。





私たちは、テストのパフォーマンスを比較してみましょう:

地図のサイズ: 10.000 100.000 1.000.000 10.000.000 100.000.000
KolobokeMap 1867 2471 3129 7546 11191
IntIntMap1 2768 3671 6105 12313 16073
Intintnap2 2254 2767 4869 10543 16724
Intintnap3 2050 2269 3548 9074 13750

あなたはハッシュテーブルのサイズの増加の影響で、見ることができるように(大きいあなたのハッシュテーブル、CPUのキャッシュヒットにあまりヘルプ)。我々はKolobokeから遠く離れているが、それは私の以前の記事で第三位にランクされてきたが、以下のKolobokeとFastUtilが続きました。

バージョン4および4a:キーと値の配列の代わりに、アレイを使用して

今、私たちはキーと値を格納するための単一のアレイを使用したい - このステップは、前のステップの方向をたどります。これは、彼らが次のキーに配置されますので、非常に低コストで値を変更/アクセスすることが可能となります。

2つの可能な実装があります。

1.用一个 long[] ,一个 key 和 一个 value 将共享一个 long 。这种方法的有效性仅限于某些类型的 key 和 value。
2.用一个 int[] ,keys 和 values 将间隔的分布。这个方法缺点就是容量只有10亿,我相信对于大多数场景不是问题。

这两种情况之间的区别在于需要使用位算术/类型转换来从 long 数组中提取 key 和 value。我的测试显示这些操作对哈希表的性能有显着的负面影响。不过我已经将 long [](IntIntMap4)和int [](IntIntMap4a)版本都包含到本文中。

小优化点

两个版本都会非常快,但你需要更多优化才能成为最快的版本。你应该了解一个hashmap,它的基本操作具有O(1)复杂度,这一点很重要,只要你不太过于追求填充因子。 这实际上意味着你必须计算散列命中路径上的指示(您检查的第一个单元,如果是空的或包含请求的 key)。优化哈希碰撞循环也很重要,但是(我重复这一点),您必须非常小心哈希命中路径,因为大多数操作最终都会以哈希命中结束。

考虑到这一点,您可能想要内联一些辅助方法,尤其是那些可以在内联时为您节省指令的方法。 例如,看一下上一个版本的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public int get( final int key )
{
if ( key == FREE_KEY )
return m_hasFreeKey ? m_freeValue : NO_VALUE;

final int idx = getReadIndex( key );
return idx != -1 ? m_values[ idx ] : NO_VALUE;
}

private int getReadIndex( final int key )
{ INT IDX = getStartIndex(キー)。もし(m_keys [IDX] ==キー)//私たちは前にこのコールに無料でチェックリターン IDXを、もし(m_keys [IDX] == FREE_KEY)鎖の末端//既に返す - 1最終int型 startIdx = IDX。一方、(!(IDX = getNextIndex(IDX))= startIdx) { 場合(m_keys [IDX] == FREE_KEY)リターン - 1もし(m_keys [IDX] ==キー)リターン IDX。 } リターン - 1}















ほとんどの場合、裁判官は、最初の行は、裁判官に、ここでそれができない、真です。

おすすめ

転載: www.cnblogs.com/liuzhongrong/p/11874919.html