1. Gambaran keseluruhan
The HyperLogLog (PPL) struktur data adalah struktur data kebarangkalian digunakan untuk menganggarkan cardinality daripada satu set data .
Anggaplah kita mempunyai berjuta-juta pengguna dan kita ingin mengira jumlah lawatan berbeza ke laman web kita. Pelaksanaan yang naif adalah menyimpan setiap id pengguna unik dalam satu set, dan kemudian ukuran set itu menjadi kardinaliti kita.
Ketika kita berhadapan dengan jumlah data yang sangat besar, menghitung kardinaliti dengan cara ini akan sangat tidak efisien kerana set data akan memakan banyak memori.
Tetapi jika kita baik dengan perkiraan dalam beberapa persen dan tidak memerlukan jumlah kunjungan unik yang tepat, maka kita dapat menggunakan HLL , seperti yang dirancang untuk kes penggunaan yang tepat - mengira jumlah jutaan atau bahkan berbilion nilai yang berbeza .
2. Ketergantungan Maven
Untuk memulakan, kita perlu menambahkan kebergantungan Maven untuk pustaka hll :
net.agkn hll 1.6.0
3. Menganggar Kardinaliti Menggunakan HLL
Melompat tepat - konstruktor HLL mempunyai dua hujah yang boleh kita ubah mengikut keperluan kita:
- log2m (log log 2) - ini adalah jumlah daftar yang digunakan secara dalaman oleh HLL (nota: kami menentukan m )
- regwidth - ini adalah bilangan bit yang digunakan setiap daftar
Sekiranya kita mahukan ketepatan yang lebih tinggi, kita perlu menetapkannya ke nilai yang lebih tinggi. Konfigurasi sedemikian akan mempunyai overhead tambahan kerana HLL kami akan mengambil lebih banyak memori. Sekiranya kita baik-baik saja dengan ketepatan yang lebih rendah, kita dapat menurunkan parameter tersebut, dan HLL kita akan menghabiskan lebih sedikit memori.
Mari buat HLL untuk mengira nilai yang berbeza untuk set data dengan 100 juta entri. Kami akan menetapkan parameter log2m sama dengan 14 dan lebar lebar sama dengan 5 - nilai munasabah untuk set data dengan ukuran ini.
Apabila setiap elemen baru dimasukkan ke HLL , ia perlu dicincang terlebih dahulu. Kami akan menggunakan Hashing.murmur3_128 () dari perpustakaan Jambu (disertakan dengan ketergantungan hll ) kerana kedua-duanya tepat dan cepat.
HashFunction hashFunction = Hashing.murmur3_128(); long numberOfElements = 100_000_000; long toleratedDifference = 1_000_000; HLL hll = new HLL(14, 5);
Memilih parameter tersebut akan memberi kita kadar kesalahan di bawah satu peratus (1,000,000 elemen). Kami akan menguji ini sebentar lagi.
Seterusnya, mari kita masukkan 100 juta elemen:
LongStream.range(0, numberOfElements).forEach(element -> { long hashedValue = hashFunction.newHasher().putLong(element).hash().asLong(); hll.addRaw(hashedValue); } );
Akhirnya, kami dapat menguji bahawa kardinaliti yang dikembalikan oleh HLL berada dalam ambang ralat yang kami mahukan :
long cardinality = hll.cardinality(); assertThat(cardinality) .isCloseTo(numberOfElements, Offset.offset(toleratedDifference));
4. Ukuran Memori HLL
Kita dapat mengira berapa banyak memori yang akan diambil oleh HLL kita dari bahagian sebelumnya dengan menggunakan formula berikut: numberOfBits = 2 ^ log2m * regwidth .
Dalam contoh kami yang akan menjadi 2 ^ 14 * 5 bit (kira-kira 81000 bit atau 8100 bait). Oleh itu, mengira kardinaliti set 100 juta anggota menggunakan HLL hanya menggunakan 8100 byte memori.
Mari bandingkan ini dengan pelaksanaan set naif. Dalam pelaksanaan seperti itu, kita harus memiliki Set 100 juta nilai Panjang , yang akan menempati 100,000,000 * 8 byte = 800,000,000 byte .
Kita dapat melihat perbezaannya sangat tinggi. Dengan menggunakan HLL , kita hanya memerlukan 8100 byte, sedangkan menggunakan implementasi Set naif, kita memerlukan sekitar 800 megabait.
Apabila kita mempertimbangkan set data yang lebih besar, perbezaan antara HLL dan pelaksanaan Set naif menjadi lebih tinggi.
5. Kesatuan Dua HLL
HLL mempunyai satu harta bermanfaat semasa melakukan kesatuan . Apabila kita mengambil kesatuan dua HLL yang dibuat dari kumpulan data yang berbeza dan mengukur kardinalitasnya, kita akan mendapat ambang ralat yang sama untuk kesatuan yang kita akan dapat jika kita menggunakan HLL tunggal dan mengira nilai hash untuk semua elemen kedua-dua data set dari awal .
Perhatikan bahawa ketika kita menyatukan dua HLL, keduanya harus mempunyai parameter log2m dan regwidth yang sama untuk menghasilkan hasil yang tepat.
Mari kita uji harta itu dengan membuat dua HLL - satu diisi dengan nilai dari 0 hingga 100 juta, dan yang kedua diisi dengan nilai dari 100 juta hingga 200 juta:
HashFunction hashFunction = Hashing.murmur3_128(); long numberOfElements = 100_000_000; long toleratedDifference = 1_000_000; HLL firstHll = new HLL(15, 5); HLL secondHLL = new HLL(15, 5); LongStream.range(0, numberOfElements).forEach(element -> { long hashedValue = hashFunction.newHasher() .putLong(element) .hash() .asLong(); firstHll.addRaw(hashedValue); } ); LongStream.range(numberOfElements, numberOfElements * 2).forEach(element -> { long hashedValue = hashFunction.newHasher() .putLong(element) .hash() .asLong(); secondHLL.addRaw(hashedValue); } );
Harap perhatikan bahawa kami menyesuaikan parameter konfigurasi HLL , meningkatkan parameter log2m dari 14, seperti yang terlihat di bahagian sebelumnya, menjadi 15 untuk contoh ini, kerana kesatuan HLL yang dihasilkan akan mengandung dua kali lebih banyak elemen.
Seterusnya, mari kita satukan firstHll dan secondHll menggunakan kaedah penyatuan () . Seperti yang anda lihat, anggaran kardinaliti berada dalam ambang ralat seolah-olah kita telah mengambil kardinaliti dari satu HLL dengan 200 juta elemen:
firstHll.union(secondHLL); long cardinality = firstHll.cardinality(); assertThat(cardinality) .isCloseTo(numberOfElements * 2, Offset.offset(toleratedDifference * 2));
6. Kesimpulannya
Dalam tutorial ini, kami melihat algoritma HyperLogLog .
Kami melihat bagaimana menggunakan HLL untuk mengira kardinaliti satu set. Kami juga melihat bahawa HLL sangat cekap ruang berbanding dengan penyelesaian naif. Dan kami melakukan operasi kesatuan pada dua HLL dan mengesahkan bahawa kesatuan itu bertindak dengan cara yang sama dengan HLL tunggal .
Pelaksanaan semua contoh dan coretan kod ini boleh didapati dalam projek GitHub; ini adalah projek Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.