Teknik eksplorasi otomatis fungsi hash bilangan bulat
(github.com/skeeto)- Hash Function Prospector adalah alat yang menghasilkan fungsi hash bilangan bulat acak dalam jumlah besar, mengompilasinya dengan JIT, mengevaluasi perilaku avalanche, lalu menampilkan fungsi terbaik saat ini dalam sintaks C
- Evaluasi menggunakan avalanche score, yaitu jumlah rata-rata bit output yang tetap tidak berubah ketika satu bit input dibalik; makin rendah makin baik, dan nilai idealnya adalah 0
- Target eksplorasi adalah fungsi hash bilangan bulat 32-bit dan 64-bit; karena kompiler JIT, alat ini sendiri hanya mendukung x86-64, tetapi fungsi yang ditemukan dapat dipakai di lingkungan lain
- Fungsi-fungsi utama yang ditemukan memakai susunan xorshift-multiply-xorshift;
lowbias322 ronde menunjukkan bias lebih rendah dengan selisih kecil dibanding finalizer MurmurHash3 32-bit, dantriple323 ronde mendekati batas bias teoretis - Pengukuran bias yang akurat dapat dilakukan untuk fungsi 32-bit dengan
-Edan-e, hash 16-bit ditangani alat terpisahhp16, dan perlu berhati-hati terhadap aturan promosi integer di C
Peran Hash Function Prospector
- Hash Function Prospector adalah alat penemuan fungsi hash bilangan bulat otomatis
- Alat ini menghasilkan miliaran fungsi hash bilangan bulat secara acak, lalu mengompilasinya dengan JIT dan mengevaluasi perilaku avalanche-nya
- Di antara fungsi yang dihasilkan, fungsi terbaik saat ini ditampilkan dalam sintaks C
- Tautan terkait tersedia ke Prospecting for Hash Functions
Kriteria evaluasi dan cakupan dukungan
- avalanche score adalah jumlah rata-rata bit output yang tetap tidak berubah ketika satu bit input dibalik
- Semakin rendah nilainya, semakin baik
- Secara ideal semua bit output akan terbalik dengan probabilitas 50%, sehingga score menjadi 0
- Prospector dapat menghasilkan fungsi hash bilangan bulat 32-bit dan 64-bit
- Opsi lengkap dapat dilihat melalui penggunaan
-h - Karena kompiler JIT, alat ini sendiri hanya mendukung x86-64
- Namun fungsi hash yang ditemukan dapat digunakan di mana saja
Operasi reversibel yang dipakai dalam eksplorasi
- Generator menyusun fungsi secara acak dari 9 operasi reversibel yang dipilih
- Daftar operasinya adalah sebagai berikut
x = ~xx ^= constantx *= constant | 1x += constantx ^= x >> constantx ^= x << constantx += x << constantx -= x << constantx <<<= constantx = bswap(x)
- Secara teknis
x = ~xdapat direpresentasikan sebagaix ^= constant, tetapi kemungkinan generator memilih konstanta XOR tersebut secara kebetulan sangat kecil, sehingga diperlakukan sebagai operasi terpisah
Fungsi hash 32-bit yang ditemukan
-
Fungsi 2 ronde
- Salah satu keluarga fungsi yang berguna yang ditemukan adalah susunan xorshift-multiply-xorshift 2 ronde
- TheIronBorn menemukan parameter optimal yang diketahui untuk susunan ini dengan menggunakan optimisasi kombinatorial, dan hasilnya adalah
[16 21f0aaad 15 d35a2d97 15] = 0.10760229515479501 lowbias32adalah permutasi 32-bit 2 ronde dengan bias rendah, dan menunjukkan bias yang sedikit lebih rendah daripada finalizer MurmurHash3 32-bit- Exact bias dari
lowbias32adalah0.17353355999581582 - Susunannya ditemukan oleh Prospector, dan parameternya disetel dengan hill climbing serta algoritme genetika
- Fungsi invers
lowbias32_rjuga disediakan prospector32adalah fungsi yang ditemukan hanya dengan menggunakan Prospector- Exact bias-nya adalah
0.34968228323361017 - Bias ini lebih besar daripada
lowbias32sebelumnya - Untuk mengeksplorasi konstanta perkalian alternatif secara acak, pola dapat ditentukan seperti berikut
./prospector -p xorr:15,mul,xorr:12,mul,xorr:15
-
Fungsi 3 ronde
- Jika satu ronde multiply-xorshift lagi ditambahkan ke susunan yang sama, parameter yang dipilih dengan cermat dapat mencapai batas bias teoretis
triple32memiliki exact bias sebesar0.020888578919738908- README menjelaskan bahwa fungsi ini tak dapat dibedakan dari PRF sempurna seperti permutasi acak atas semua integer 32-bit
- Fungsi invers
triple32_rjuga disediakan - Daftar konstanta 3 ronde mencakup hasil bias rendah dari
0.020888578919738908hingga sekitar0.022984943828687553 triple32inc, yaitutriple32yang didahului operasi increment, memecahkan masalahhash(0) = 0dan juga sedikit menurunkan bias- Exact bias-nya adalah
0.020829410544597495 - Fungsi invers
triple32inc_rmenjalankanx--di bagian akhir
Pengukuran exact bias
- Mode
-Emengevaluasi bias dari fungsi hash yang diberikan - Secara default Prospector menggunakan estimasi untuk mengevaluasi bias dengan cepat
- Estimasi ini tidak deterministik dan hasilnya sangat berisik
- Untuk mengukur exact bias dengan pencarian menyeluruh, gunakan opsi
-e - Fungsi yang akan diperiksa dapat didefinisikan dengan dua cara
- Didefinisikan dengan
-pdan pola - Didefinisikan dengan
-ldan shared library yang berisi fungsihash()
- Didefinisikan dengan
- Metode shared library memungkinkan pengujian fungsi hash yang tidak dapat direpresentasikan dengan ekspresi fungsi Prospector yang terbatas
- Input default diperlakukan sebagai fungsi hash 32-bit
- Switch
-8menguji fungsi 64-bit dengan metode estimasi- Fungsi hash 64-bit memerlukan waktu terlalu lama sehingga tidak ada exact exhaustive test
hp16 untuk hash 16-bit
- Hash 16-bit memiliki kendala yang berbeda, sehingga disediakan alat terpisah
hp16 - Tidak seperti Prospector 32-bit dan 64-bit,
hp16sepenuhnya portabel dan dapat dijalankan di hampir semua sistem hp16juga dapat membuat dan mengevaluasi s-box 128KiB- Karena hash 16-bit mungkin dibutuhkan pada mesin tanpa instruksi perkalian cepat, tersedia juga opsi untuk menghilangkan operasi tertentu saat eksplorasi
-m-r
Hasil 16-bit dan perhatian pada implementasi C
- Contoh hasil 16-bit saat ini adalah sebagai berikut
- 2 ronde xorshift-multiply
hash16_xm2: bias0.0085905051336723701 - 3 ronde xorshift-multiply
hash16_xm3: bias0.0045976709018820602 - Tanpa perkalian
hash16_s6: bias0.023840118344741465
- 2 ronde xorshift-multiply
hash16_s6tanpa perkalian disebut setara dengan bentuk xorshift-multiply tertentu- Hash xorshift 3 ronde yang baik dari eksplorasi singkat dengan
hp16 -Xn3merupakan pendekatan yang dekat dengan s-box baik darihp16 -S - Saat menulis operasi 16-bit dalam C, perlu berhati-hati terhadap aturan promosi integer
- Misalnya, pada implementasi 32-bit, operan unsigned 16-bit dapat dipromosikan menjadi integer 32-bit signed
- Dalam kasus ini, hasil yang salah dapat muncul pada situasi tertentu
- Kode C yang dikeluarkan program ini berhati-hati untuk mempromosikan operasi 16-bit menjadi
unsigned intdi tempat yang diperlukan
1 komentar
Komentar Hacker News
Saya tidak mengenalnya secara pribadi, tetapi saya suka kodenya
Terutama library JSON https://github.com/skeeto/pdjson, library parsing opsi https://github.com/skeeto/optparse dan https://github.com/skeeto/getopt, decoder UTF-8 tanpa percabangan https://github.com/skeeto/branchless-utf8, stack bebas-lock https://github.com/skeeto/lstack, serta library trie https://github.com/skeeto/trie
Saya juga suka preferensi lisensinya: semua proyek di atas didistribusikan dengan The Unlicense
Saya sudah mengikutinya di GitHub selama bertahun-tahun, dan ia selalu merilis tool kecil dan unik untuk ceruk yang aneh. Misalnya Branchless UTF-8 yang terkenal
Halo, saya pembuat MurmurHash. Ini pekerjaan yang menarik, dan lucu juga melihat pendekatan multiply-shift-XOR bertahan begitu baik selama ini
Namun ide avalanche + bias tampaknya masih cukup banyak kekurangannya. Misalnya fungsi
triple32yang dicantumkan di bagian akhir memiliki bias persis0.020888578919738908, dan jika FabriceNeyret2 mengimplementasikannya di ShaderToy, hasilnya menjadi gambar seperti ini: https://www.shadertoy.com/view/WttXWX atau https://i.imgur.com/qU2P5rx.pngTetapi jika melakukan diferensiasi kemiringan normal map sederhana, terlihat cukup banyak garis “kristal” yang mencolok. Mungkin ada istilah teknis untuk bentuk ridge seperti ini: https://i.imgur.com/IHWT1GM.png
Sebagai tambahan, saya rasa keseluruhan ide ini sudah sekitar 5 tahun lamanya: https://nullprogram.com/blog/2018/07/31/
Karena pengalaman mengembangkan fungsi hash yang bagus, saya sering memikirkan ide pencarian hash otomatis
Senang melihat pekerjaan seperti ini. Akan bagus jika dihubungkan dengan SMHasher3, varian yang jauh lebih baik dan lebih cepat dari rangkaian pengujian hash lama buatan Frank J. T. Wojcik, lalu hasil keluarannya dievaluasi secara otomatis. Demi kecepatan, sebagian pengujian saja bisa digunakan dan dibuat cepat gagal
Memperluasnya ke hash 64-bit dan 128-bit juga bagus, tetapi tentu ruang pencariannya menjadi lebih besar. Terkait itu, saya pernah membuat kode NodeJS yang mengukur avalanche pada perkalian bilangan prima 64-bit untuk memilih nilai yang akan dipakai di Rain
[Rain]: https://github.com/dosyago/rain
[SMHasher3]: https://gitlab.com/fwojcik/smhasher3
Akan menarik jika ini digeneralisasi ke operasi yang tersedia di ekstensi manipulasi bit RISC-V. Mungkin suatu saat akan ditemukan fungsi-fungsi kuat yang bisa dipakai ketika instruksi-instruksi itu makin tersebar luas
Perkalian tanpa carry juga dapat memperluas himpunan operasi yang dapat dibalik, dan cepat pada sebagian hardware yang sudah ada. CRC juga agak terkait, tetapi tersedia pada kumpulan hardware yang lebih luas, dan semestinya merupakan subset ketat dari apa yang bisa ditemukan CLMUL
Banyak penggunaan hash hanya peduli pada bit paling rendah atau bit paling tinggi dari nilai hash, jadi menarik juga untuk mengevaluasi bias pada rentang bit tertinggi/terendah atau sisa pembagian oleh berbagai bilangan. Fungsi yang tampak tidak bias jika dilihat dari keseluruhan output bisa menjadi lebih baik atau lebih buruk pada metrik yang tidak melihat keseluruhan output, atau pada input yang tidak seragam seperti teks ASCII
Bisa jelaskan kenapa ini keren dan dipakai untuk apa?
Metrik tujuannya tampaknya adalah apakah saat satu bit input berubah, sebanyak mungkin bit output berubah seacak mungkin. Alat ini mengeluarkan kode C untuk fungsi hash terbaik dari yang dihasilkannya
Jadi ini berguna ketika kita membutuhkan fungsi hash tetapi merasa fungsi yang ada belum cukup bagus, atau saat meneliti fungsi hash dan membutuhkan ide struktur baru. Pembuatan kodenya sendiri sudah keren, dan melakukannya secara acak adalah langkah pertama menuju genetic programming yang lebih keren lagi. Selain itu, manusia tampaknya sejak sekitar 15 tahun lalu suka membuat komputer membakar siklus CPU untuk menghitung hash yang kemungkinan besar tidak akan pernah dipakai
Hash table adalah struktur data hebat yang memungkinkan banyak algoritma diimplementasikan secara sederhana dan efisien. Efisiensi ini bergantung pada apakah kita bisa membuat hash yang kecil untuk data, misalnya 32-bit atau 64-bit, dan hampir unik
Misalnya, saat melakukan hash pada nama pengguna, jika hanya memakai kode ASCII huruf pertama dari nama, banyak nama pengguna akan dipetakan ke angka yang sama sehingga tidak bekerja dengan baik. Ini disebut collision, dan jika collision banyak, hash table menjadi sangat tidak efisien
Cara yang lebih baik adalah mengambil bit dari seluruh nama pengguna lalu mencampurnya dengan suatu cara agar
throwaway_1237danthrowaway_12373menjadi angka yang berbeda. Fungsi hash melakukan pemetaan ini, dan sifat avalanche menjelaskan seberapa baik ia menghindari collisionBiasanya ada kompromi antara seberapa cepat fungsi hash nyata dan seberapa baik ia menghindari collision. Fungsi hash kelas dunia terlihat cukup aneh, seperti mengalikan dengan konstanta ganjil, melakukan XOR, dan shift; sangat sulit bagi manusia menebak performanya hanya dengan melihat fungsi yang sulit dipahami seperti ini
Kode ini mencoba berbagai fungsi hash secara acak dan membuatnya saling bersaing. Kalau berhasil, ini keren karena bisa meningkatkan performa nyata struktur data inti yang dipakai di berbagai bahasa dan library
Beberapa minggu lalu saya mengimplementasikan 1brc di Go https://github.com/infogulch/1brc-go, dan setelah melihat repositori ini saya terinspirasi untuk mencari fungsi perfect hash kustom agar setiap stasiun pengamatan masuk ke bucket-nya sendiri tanpa collision
Namun saya membatalkan ide itu setelah melihat aturan bahwa fungsi hash tidak boleh dikustomisasi berdasarkan data sebelum program dimulai
Saya membuat perangkat uji yang memeriksa konstanta acak, nilai awal, konstanta perkalian, jumlah shift/rotasi, dan sebagainya, lalu mencetak konstanta terbaik sejauh ini berdasarkan jumlah bucket yang collision dan jumlah collision. Seingat saya, pada load factor sekitar 40%, saya berhasil menurunkannya hingga hanya dua nilai yang collision di satu bucket saja. Menariknya, konstanta dengan performa terbaik, terlepas dari konstanta lain, mengandung jumlah posisi shift yang mirip, jadi pada akhirnya nilai-nilai itu saya hard-code
Akan sangat menarik kalau kita bisa memasukkan generator data input sendiri. Dalam praktiknya, data sering kali bukan data biner acak, melainkan data yang terstruktur dengan suatu cara, dan struktur itu mungkin memungkinkan kita memperoleh fungsi hash yang sangat bagus
Membatasi ke operasi reversibel punya kelebihan matematis, tetapi pada saat yang sama mengecualikan banyak hal
Saat saya melakukan sesuatu yang mirip, saya sedang memikirkan perfect hashing di mana kumpulan input sudah diketahui sebelumnya. Pendekatan umum memakai array konstanta, tetapi saya ingin melihat apakah bisa dibuat lebih ringkas, terutama jika inputnya sudah berupa integer kecil. Tentu saja bisa dilakukan dengan sesuatu seperti
hash -= hash >> gap_indexJadi saya mencoba daftar sekitar 100 operasi primitif. Sebagiannya saling tumpang tindih, tetapi berguna jika dipikirkan secara terpisah. Lalu saya bosan dan tidak menjadikannya proyek apa pun
Saya tidak begitu paham apa tepatnya yang dilakukan. Apakah ini mencari yang terbaik sepanjang masa? Kalau bukan, saya penasaran kenapa nilai terbaik berubah setiap kali dijalankan
Saya juga penasaran apakah ada yang tahu mekanisme untuk menemukan fungsi hash yang bagus ketika kita tahu nilai integer hanya muncul dalam rentang tertentu, misalnya antara 10.000 dan 200.000, agar nilai itu masuk ke jumlah bucket hash yang optimal
Menyapu seluruh ruang pencarian dalam satu kali eksekusi untuk menemukan nilai optimal absolut tidak realistis, dan karena urutan percobaannya juga acak, nilainya bisa berbeda tiap kali dijalankan
Jika yang dibutuhkan hanya hash yang “bagus”, hampir selalu pilihan terbaik adalah memakai fungsi hash umum. Jika angkanya sangat besar dan rentangnya sangat kecil, kita bisa menerapkan offset agar nilai minimum kembali menjadi 0, lalu memakai hash yang lebih kecil dan cepat. Jika ingin menemukan “pilihan sempurna” untuk rentang yang tepat, pendekatan acak seperti ini tampaknya yang paling mendekati, dan pengujiannya tinggal diubah agar dilakukan pada rentang tersebut
Saya penasaran apakah memakai konstanta yang sama untuk dua perkalian bisa mengurangi ukuran kode sehingga perhitungannya juga mungkin sedikit lebih cepat
Saya juga memperbarui jawaban StackOverflow: https://stackoverflow.com/questions/664014/what-integer-hash...