Apakah mungkin untuk mengimplementasikan tabel hash yang didistribusikan dengan baik tanpa menggunakan operator%?

11

Saya mencari untuk mengimplementasikan tabel hash yang cepat dan didistribusikan dengan baik di C #. Saya mengalami masalah dalam memilih fungsi hash-constraining yang mengambil kode hash sembarang dan "membatasi" sehingga dapat digunakan untuk mengindeks ember. Ada dua opsi yang saya lihat sejauh ini:

  • Di satu sisi, Anda dapat memastikan ember Anda selalu memiliki jumlah elemen utama, dan untuk membatasi hash Anda cukup memodonya dengan jumlah ember. Inilah sebenarnya yang dilakukan oleh .NET's Dictionary . Masalah dengan pendekatan ini adalah bahwa menggunakan% sangat lambat dibandingkan dengan operasi lain; jika Anda melihat tabel instruksi Agner Fog , idiv(yang merupakan kode rakitan yang dihasilkan untuk%) memiliki latensi instruksi ~ 25 siklus untuk prosesor Intel yang lebih baru. Bandingkan ini dengan sekitar 3 untuk mul, atau 1 untuk ops bitwise seperti and, or, atau xor.

  • Di sisi lain, Anda dapat memiliki jumlah ember selalu menjadi kekuatan 2. Anda masih harus menghitung modulus hash sehingga Anda tidak berusaha untuk mengindeks di luar array, tetapi kali ini akan lebih murah . Karena untuk kekuatan 2 % Nhanya & (N - 1), pembatas direduksi menjadi operasi masking yang hanya membutuhkan 1-2 siklus. Ini dilakukan oleh Google sparsehash . Kelemahan dari ini adalah bahwa kami mengandalkan pengguna untuk menyediakan hash yang baik; menutupi hash pada dasarnya memotong sebagian dari hash, jadi kita tidak lagi memperhitungkan semua bagian dari hash. Jika hash pengguna tidak terdistribusi secara merata, misalnya hanya bit yang lebih tinggi yang diisi atau bit yang lebih rendah secara konsisten sama, maka pendekatan ini memiliki tingkat tabrakan yang jauh lebih tinggi.

Saya mencari algoritme yang dapat saya gunakan yang memiliki yang terbaik dari kedua dunia: ia mengambil semua bit hash ke dalam akun, dan juga lebih cepat daripada menggunakan%. Itu tidak harus harus menjadi modulus, hanya sesuatu yang dijamin berada dalam kisaran 0..N-1(di mana N adalah panjang ember) dan memiliki distribusi yang merata untuk semua slot. Apakah ada algoritma seperti itu?

Terima kasih telah membantu.

James Ko
sumber
1
Lihat efek longsoran salju , serta penjelasannya di murmurhash3 (smhasher) . Namun, poin mendasar dalam pertanyaan Anda tidak diatasi dengan mengadopsi fungsi hash yang lebih baik. Sebaliknya, ini adalah pertanyaan tentang mengapa pengguna tidak mengadopsi fungsi hash yang sama lebih baik di tempat pertama, dan ajakan untuk penanggulangan (seolah-olah pengguna malas malas).
rwong
Untuk modulo cepat (2^N +/- 1), lihat stackoverflow.com/questions/763137/…
rwong
@rwong Saya minta maaf, tapi saya tidak yakin apa komentar Anda tentang postingan saya. Saya tidak mengontrol hash yang disediakan oleh pengguna, jadi saya tidak mencari fungsi hash yang lebih baik. Saya juga tidak mengerti apa yang Anda maksud dengan "pengguna malas yang jahat."
James Ko
4
Jika fungsi hash buruk, tidak ada yang dapat dilakukan oleh implementasi tabel hash untuk "memperbaiki" distribusi yang buruk. Modulo bilangan prima tidak memperbaiki hash yang buruk. Pertimbangkan fungsi hash yang menghasilkan sebagai output, kelipatan dari bilangan prima. Saya telah melihat masalah seperti itu dalam kode produksi nyata.
Frank Hileman

Jawaban:

9

Implementasi tabel hash modern tidak menggunakan fungsi modulo. Mereka sering menggunakan kekuatan dua tabel ukuran dan memotong bit yang tidak dibutuhkan. Fungsi hash yang ideal akan memungkinkan ini. Penggunaan modulo dikombinasikan dengan ukuran tabel bilangan prima muncul pada hari-hari ketika fungsi hash umumnya buruk, karena mereka sering dalam pengembangan .net. Saya merekomendasikan membaca tentang SipHash , fungsi hash modern, kemudian membaca tentang beberapa fungsi modern lainnya, seperti xxHash .

Saya harus menjelaskan mengapa. Fungsi hash bersih sering buruk. Di .net, programmer sering dipaksa untuk mengimplementasikan fungsi hash dengan mengesampingkan GetHashcode. Tetapi .net tidak menyediakan alat yang diperlukan untuk memastikan fungsi yang dibuat programmer berkualitas tinggi, yaitu:

  • enkapsulasi negara hash dalam struktur atau kelas
  • fungsi "tambah" hash, yang menambahkan data baru ke status hash (tambahkan array byte, atau ganda, misalnya)
  • fungsi "menyelesaikan" hash, untuk menghasilkan longsoran salju
  • enkapsulasi hasil hash - .net Anda mendapatkan satu pilihan, integer ditandatangani 32 bit.

Untuk informasi lebih lanjut tentang menggunakan hasil fungsi hash sebagai indeks tabel hash, silakan lihat definisi bentuk universal hashing dalam makalah ini: hashing universal 64-bit lebih cepat menggunakan multiplikasi carry-less

Frank Hileman
sumber
3

Untuk menggunakan DAN sambil tetap menyimpan semua bit, gunakan XOR juga.

Sebagai contoh temp = (hash & 0xFFFF) ^ ( hash >> 16); index = (temp & 0xFF) ^ (temp >> 8);,.

Untuk contoh ini, tidak ada modulo dan semua 32 bit hashefek 8-bit index. Namun, apakah itu lebih cepat daripada DIV adalah sesuatu yang tergantung pada terlalu banyak faktor, dan dapat dengan mudah lebih lambat daripada DIV dalam beberapa kasus (misalnya hash besar dan indeks kecil).

Brendan
sumber
Ini akan selalu lebih cepat daripada DIV / IDIV, namun saya tidak berpikir itu menjawab pertanyaan saya - indexakan berada dalam jangkauan [0..255]. Saya butuh sesuatu di kisaran [0..n-1], di mana njumlah ember.
James Ko
@JamesKo Tetapi jika Anda menerapkan kamus, Anda juga mengontrol jumlah ember (hingga tingkat tertentu). Jadi, alih-alih bilangan prima, Anda bisa memilih kekuatan dua. (Apakah melakukan itu sebenarnya ide yang baik, saya tidak bisa memberi tahu Anda.)
svick
@vick Untuk kekuatan 2 kita bisa melakukan operasi topeng sederhana. Seperti yang disebutkan dalam pertanyaan, saya mencari cara yang murah untuk melakukan ini dengan bilangan prima sehingga bahkan hash yang didistribusikan tidak tertampung.
James Ko
1

Anda dapat mengambil keuntungan dari kenyataan bahwa banyak bilangan bulat utama memiliki pembalikan multiplikasi modular. Lihat artikel ini . Anda telah memenuhi salah satu kendala dengan menjadikan indeks bucket Anda prima dan modulus 2, yang secara inheren relatif prima.

Artikel ini menjelaskan algoritma untuk menemukan angka sedemikian sehingga mengalikannya dengan angka itu, dan mengabaikan luapan, akan menghasilkan hasil yang sama seolah-olah Anda telah dibagi dengan ukuran indeks ember.

BobDalgleish
sumber