1use num_primitive::{PrimitiveNumber, PrimitiveNumberAs};
9use std::hash::*;
10use std::num::NonZeroUsize;
11use std::{borrow::Borrow, f64::consts::LN_2};
12
13use crate::traits::Word;
14
15use crate::traits::{EstimationLogic, MergeEstimationLogic, SliceEstimationLogic};
16
17use super::DefaultEstimator;
18
19type HashResult = u64;
21
22#[derive(Debug, PartialEq)]
44pub struct HyperLogLog<T, H, W = usize> {
45 build_hasher: H,
46 register_size: usize,
47 num_registers_minus_1: HashResult,
48 log_2_num_registers: usize,
49 sentinel_mask: HashResult,
50 num_registers: usize,
51 pub(super) words_per_estimator: usize,
52 alpha_m_m: f64,
53 msb_mask: Box<[W]>,
54 lsb_mask: Box<[W]>,
55 _marker: std::marker::PhantomData<T>,
56}
57
58impl<T, H: Clone, W: Clone> Clone for HyperLogLog<T, H, W> {
61 fn clone(&self) -> Self {
62 Self {
63 build_hasher: self.build_hasher.clone(),
64 register_size: self.register_size,
65 num_registers_minus_1: self.num_registers_minus_1,
66 log_2_num_registers: self.log_2_num_registers,
67 sentinel_mask: self.sentinel_mask,
68 num_registers: self.num_registers,
69 words_per_estimator: self.words_per_estimator,
70 alpha_m_m: self.alpha_m_m,
71 msb_mask: self.msb_mask.clone(),
72 lsb_mask: self.lsb_mask.clone(),
73 _marker: std::marker::PhantomData,
74 }
75 }
76}
77
78impl<T, H: Clone, W: Word> HyperLogLog<T, H, W> {
79 #[inline(always)]
81 fn get_register_unchecked(&self, backend: impl AsRef<[W]>, index: usize) -> W {
82 let backend = backend.as_ref();
83 let bits = W::BITS as usize;
84 let bit_width = self.register_size;
85 let mask = W::MAX >> (bits - bit_width);
86 let pos = index * bit_width;
87 let word_index = pos / bits;
88 let bit_index = pos % bits;
89
90 if bit_index + bit_width <= bits {
91 (unsafe { *backend.get_unchecked(word_index) } >> bit_index) & mask
92 } else {
93 ((unsafe { *backend.get_unchecked(word_index) } >> bit_index)
94 | (unsafe { *backend.get_unchecked(word_index + 1) } << (bits - bit_index)))
95 & mask
96 }
97 }
98
99 #[inline(always)]
101 fn set_register_unchecked(&self, mut backend: impl AsMut<[W]>, index: usize, new_value: W) {
102 let backend = backend.as_mut();
103 let bits = W::BITS as usize;
104 let bit_width = self.register_size;
105 let mask = W::MAX >> (bits - bit_width);
106 let pos = index * bit_width;
107 let word_index = pos / bits;
108 let bit_index = pos % bits;
109
110 if bit_index + bit_width <= bits {
111 let mut word = unsafe { *backend.get_unchecked_mut(word_index) };
112 word &= !(mask << bit_index);
113 word |= new_value << bit_index;
114 unsafe { *backend.get_unchecked_mut(word_index) = word };
115 } else {
116 let mut word = unsafe { *backend.get_unchecked_mut(word_index) };
117 word &= (W::ONE << bit_index) - W::ONE;
118 word |= new_value << bit_index;
119 unsafe { *backend.get_unchecked_mut(word_index) = word };
120
121 let mut word = unsafe { *backend.get_unchecked_mut(word_index + 1) };
122 word &= !(mask >> (bits - bit_index));
123 word |= new_value >> (bits - bit_index);
124 unsafe { *backend.get_unchecked_mut(word_index + 1) = word };
125 }
126 }
127}
128
129impl<T: Hash, H: BuildHasher + Clone, W: Word> SliceEstimationLogic<W> for HyperLogLog<T, H, W>
130where
131 u32: PrimitiveNumberAs<W>,
132{
133 #[inline(always)]
134 fn backend_len(&self) -> usize {
135 self.words_per_estimator
136 }
137}
138
139impl<T: Hash, H: BuildHasher + Clone, W: Word> EstimationLogic for HyperLogLog<T, H, W>
140where
141 u32: PrimitiveNumberAs<W>,
142{
143 type Item = T;
144 type Backend = [W];
145 type Estimator<'a>
146 = DefaultEstimator<Self, &'a Self, Box<[W]>>
147 where
148 T: 'a,
149 W: 'a,
150 H: 'a;
151
152 fn new_estimator(&self) -> Self::Estimator<'_> {
153 Self::Estimator::new(
154 self,
155 vec![W::ZERO; self.words_per_estimator].into_boxed_slice(),
156 )
157 }
158
159 fn add(&self, backend: &mut Self::Backend, element: impl Borrow<T>) {
160 let x = self.build_hasher.hash_one(element.borrow());
161 let j = x & self.num_registers_minus_1;
162 let r = ((x >> self.log_2_num_registers) | self.sentinel_mask)
165 .trailing_zeros()
166 .as_to();
167 let register = j as usize;
168
169 debug_assert!(r < (W::ONE << self.register_size) - W::ONE);
170 debug_assert!(register < self.num_registers);
171
172 let current_value = self.get_register_unchecked(&*backend, register);
173 let candidate_value = r + W::ONE;
174 let new_value = std::cmp::max(current_value, candidate_value);
175 if current_value != new_value {
176 self.set_register_unchecked(backend, register, new_value);
177 }
178 }
179
180 fn estimate(&self, backend: &[W]) -> f64 {
181 let mut harmonic_mean = 0.0;
182 let mut zeroes = 0;
183
184 for i in 0..self.num_registers {
185 let value: u32 = self.get_register_unchecked(backend, i).as_to();
187 if value == 0 {
188 zeroes += 1;
189 }
190 harmonic_mean += 1.0 / (1_u64 << value) as f64;
191 }
192
193 let mut estimate = self.alpha_m_m / harmonic_mean;
194 if zeroes != 0 && estimate < 2.5 * self.num_registers as f64 {
195 estimate = self.num_registers as f64 * (self.num_registers as f64 / zeroes as f64).ln();
196 }
197 estimate
198 }
199
200 #[inline(always)]
201 fn clear(&self, backend: &mut [W]) {
202 backend.fill(W::ZERO);
203 }
204
205 #[inline(always)]
206 fn set(&self, dst: &mut [W], src: &[W]) {
207 debug_assert_eq!(dst.len(), src.len());
208 dst.copy_from_slice(src);
209 }
210}
211
212pub struct HyperLogLogHelper<W> {
214 acc: Vec<W>,
215 mask: Vec<W>,
216}
217
218impl<T: Hash, H: BuildHasher + Clone, W: Word> MergeEstimationLogic for HyperLogLog<T, H, W>
219where
220 u32: PrimitiveNumberAs<W>,
221{
222 type Helper = HyperLogLogHelper<W>;
223
224 fn new_helper(&self) -> Self::Helper {
225 HyperLogLogHelper {
226 acc: vec![W::ZERO; self.words_per_estimator],
227 mask: vec![W::ZERO; self.words_per_estimator],
228 }
229 }
230
231 #[inline(always)]
232 fn merge_with_helper(&self, dst: &mut [W], src: &[W], helper: &mut Self::Helper) {
233 merge_hyperloglog_bitwise(
234 dst,
235 src,
236 self.msb_mask.as_ref(),
237 self.lsb_mask.as_ref(),
238 &mut helper.acc,
239 &mut helper.mask,
240 self.register_size,
241 );
242 }
243}
244
245#[derive(Debug, Clone)]
247pub struct HyperLogLogBuilder<H, W = usize> {
248 build_hasher: H,
249 log_2_num_registers: usize,
250 num_elements: usize,
251 _marker: std::marker::PhantomData<W>,
252}
253
254impl HyperLogLogBuilder<BuildHasherDefault<DefaultHasher>> {
255 pub const fn new(num_elements: usize) -> Self {
262 assert!(
263 num_elements > 0,
264 "the upper bound on the number of distinct elements must be positive"
265 );
266 Self {
267 build_hasher: BuildHasherDefault::new(),
268 log_2_num_registers: 4,
269 num_elements,
270 _marker: std::marker::PhantomData,
271 }
272 }
273}
274
275fn min_alignment(bits: usize) -> String {
276 if bits % 128 == 0 {
277 "u128"
278 } else if bits % 64 == 0 {
279 "u64"
280 } else if bits % 32 == 0 {
281 "u32"
282 } else if bits % 16 == 0 {
283 "u16"
284 } else {
285 "u8"
286 }
287 .to_string()
288}
289
290impl HyperLogLog<(), (), ()> {
291 pub fn log_2_num_of_registers(rsd: f64) -> usize {
297 ((1.106 / rsd).powi(2)).log2().ceil() as usize
298 }
299
300 pub fn rel_std(log_2_num_registers: usize) -> f64 {
308 let tmp = match log_2_num_registers {
309 4 => 1.106,
310 5 => 1.070,
311 6 => 1.054,
312 7 => 1.046,
313 _ => 1.04,
314 };
315 tmp / ((1 << log_2_num_registers) as f64).sqrt()
316 }
317
318 pub fn register_size(num_elements: usize) -> usize {
324 std::cmp::max(
325 5,
326 (((num_elements as f64).ln() / LN_2) / LN_2).ln().ceil() as usize,
327 )
328 }
329}
330
331impl<H, W: Word> HyperLogLogBuilder<H, W> {
332 pub fn rsd(self, rsd: f64) -> Self {
347 self.log_2_num_reg(HyperLogLog::log_2_num_of_registers(rsd))
348 }
349
350 pub const fn log_2_num_reg(mut self, log_2_num_registers: usize) -> Self {
364 assert!(
365 log_2_num_registers >= 4,
366 "the logarithm of the number of registers per estimator should be at least 4"
367 );
368 self.log_2_num_registers = log_2_num_registers;
369 self
370 }
371
372 pub fn min_log_2_num_reg(&self) -> usize {
375 let register_size = HyperLogLog::register_size(self.num_elements);
376 let register_size = NonZeroUsize::try_from(register_size).expect("register_size is zero");
377 let min_num_regs = W::BITS / highest_power_of_2_dividing(register_size);
378 assert_eq!(min_num_regs, min_num_regs.next_power_of_two());
379 min_num_regs.trailing_zeros() as usize }
381
382 pub fn word_type<W2>(self) -> HyperLogLogBuilder<H, W2> {
390 HyperLogLogBuilder {
391 num_elements: self.num_elements,
392 build_hasher: self.build_hasher,
393 log_2_num_registers: self.log_2_num_registers,
394 _marker: std::marker::PhantomData,
395 }
396 }
397
398 pub const fn num_elements(mut self, num_elements: usize) -> Self {
404 assert!(
405 num_elements > 0,
406 "the upper bound on the number of distinct elements must be positive"
407 );
408 self.num_elements = num_elements;
409 self
410 }
411
412 pub fn build_hasher<H2>(self, build_hasher: H2) -> HyperLogLogBuilder<H2, W> {
417 HyperLogLogBuilder {
418 num_elements: self.num_elements,
419 log_2_num_registers: self.log_2_num_registers,
420 build_hasher,
421 _marker: std::marker::PhantomData,
422 }
423 }
424
425 pub fn build<T>(self) -> HyperLogLog<T, H, W> {
434 let bits = W::BITS as usize;
435 let log_2_num_registers = self.log_2_num_registers;
436 let num_elements = self.num_elements;
437 let number_of_registers = 1 << log_2_num_registers;
438 let register_size = HyperLogLog::register_size(num_elements);
439 let sentinel_mask = 1 << ((1 << register_size) - 2);
440 let alpha = match log_2_num_registers {
441 4 => 0.673,
442 5 => 0.697,
443 6 => 0.709,
444 _ => 0.7213 / (1.0 + 1.079 / number_of_registers as f64),
445 };
446 let num_registers_minus_1 = (number_of_registers - 1) as HashResult;
447
448 let est_size_in_bits = number_of_registers * register_size;
449
450 assert!(
452 est_size_in_bits % bits == 0,
453 "W should allow estimator backends to be aligned. Use {} or smaller unsigned integer types; or increase log_2_num_registers to be >= {}",
454 min_alignment(est_size_in_bits),
455 self.min_log_2_num_reg(),
456 );
457 let est_size_in_words = est_size_in_bits / bits;
458
459 let msb_mask = build_register_mask(
460 est_size_in_words,
461 register_size,
462 W::ONE << (register_size - 1),
463 );
464 let lsb_mask = build_register_mask(est_size_in_words, register_size, W::ONE);
465
466 HyperLogLog {
467 num_registers: number_of_registers,
468 num_registers_minus_1,
469 log_2_num_registers,
470 register_size,
471 alpha_m_m: alpha * (number_of_registers as f64).powi(2),
472 sentinel_mask,
473 build_hasher: self.build_hasher,
474 msb_mask,
475 lsb_mask,
476 words_per_estimator: est_size_in_words,
477 _marker: std::marker::PhantomData,
478 }
479 }
480}
481
482impl<T, H, W> std::fmt::Display for HyperLogLog<T, H, W> {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(
485 f,
486 "HyperLogLog with relative standard deviation: {}% ({} registers/estimator, {} bits/register, {} bytes/estimator)",
487 100.0 * HyperLogLog::rel_std(self.log_2_num_registers),
488 self.num_registers,
489 self.register_size,
490 (self.num_registers * self.register_size) / 8
491 )
492 }
493}
494
495fn build_register_mask<W: Word>(num_words: usize, register_size: usize, pattern: W) -> Box<[W]> {
498 let bits = W::BITS as usize;
499 let total_bits = num_words * bits;
500 let mut result = vec![W::ZERO; num_words];
501 let mut bit_pos = 0;
502 while bit_pos < total_bits {
503 let word_index = bit_pos / bits;
504 let bit_index = bit_pos % bits;
505 result[word_index] |= pattern << bit_index;
506 if bit_index + register_size > bits && word_index + 1 < num_words {
507 result[word_index + 1] |= pattern >> (bits - bit_index);
508 }
509 bit_pos += register_size;
510 }
511 result.into_boxed_slice()
512}
513
514#[inline(always)]
521pub(super) fn subtract<W: Word>(x: &mut [W], y: &[W]) {
522 debug_assert_eq!(x.len(), y.len());
523 let mut borrow = false;
524
525 for (x_word, &y) in x.iter_mut().zip(y.iter()) {
526 let mut x = *x_word;
527 if !borrow {
528 borrow = x < y;
529 } else if x != W::ZERO {
530 x = x.wrapping_sub(W::ONE);
531 borrow = x < y;
532 } else {
533 x = x.wrapping_sub(W::ONE);
534 }
535 *x_word = x.wrapping_sub(y);
536 }
537}
538
539fn merge_hyperloglog_bitwise<W: Word>(
540 mut x: impl AsMut<[W]>,
541 y: impl AsRef<[W]>,
542 msb_mask: impl AsRef<[W]>,
543 lsb_mask: impl AsRef<[W]>,
544 acc: &mut Vec<W>,
545 mask: &mut Vec<W>,
546 register_size: usize,
547) {
548 let x = x.as_mut();
549 let y = y.as_ref();
550 let msb_mask = msb_mask.as_ref();
551 let lsb_mask = lsb_mask.as_ref();
552
553 debug_assert_eq!(x.len(), y.len());
554 debug_assert_eq!(x.len(), msb_mask.len());
555 debug_assert_eq!(x.len(), lsb_mask.len());
556
557 let register_size_minus_1 = register_size - 1;
558 let num_words_minus_1 = x.len() - 1;
559 let shift_register_size_minus_1 = W::BITS as usize - register_size_minus_1;
560
561 acc.clear();
562 mask.clear();
563
564 acc.extend(
585 y.iter()
586 .zip(msb_mask)
587 .map(|(&y_word, &msb_word)| y_word | msb_word),
588 );
589
590 mask.extend(
592 x.iter()
593 .zip(msb_mask)
594 .map(|(&x_word, &msb_word)| x_word & !msb_word),
595 );
596
597 subtract(acc, mask);
599
600 acc.iter_mut()
602 .zip(x.iter())
603 .zip(y.iter())
604 .zip(msb_mask.iter())
605 .for_each(|(((acc_word, &x_word), &y_word), &msb_word)| {
606 *acc_word = ((*acc_word | (y_word ^ x_word)) ^ (y_word | !x_word)) & msb_word
607 });
608
609 {
611 let (mask_last, mask_slice) = mask.split_last_mut().unwrap();
612 let (&msb_last, msb_slice) = msb_mask.split_last().unwrap();
613 mask_slice
614 .iter_mut()
615 .zip(acc[0..num_words_minus_1].iter())
616 .zip(acc[1..].iter())
617 .zip(msb_slice.iter())
618 .rev()
619 .for_each(|(((mask_word, &acc_word), &next_acc_word), &msb_word)| {
620 *mask_word = (acc_word >> register_size_minus_1)
622 | (next_acc_word << shift_register_size_minus_1)
623 | msb_word
624 });
625 *mask_last = (acc[num_words_minus_1] >> register_size_minus_1) | msb_last;
626 }
627
628 subtract(mask, lsb_mask);
630
631 mask.iter_mut()
633 .zip(msb_mask.iter())
634 .zip(acc.iter())
635 .for_each(|((mask_word, &msb_word), &acc_word)| {
636 *mask_word = (*mask_word | msb_word) ^ acc_word
637 });
638
639 x.iter_mut()
641 .zip(y.iter())
642 .zip(mask.iter())
643 .for_each(|((x_word, &y_word), &mask_word)| {
644 *x_word = *x_word ^ ((*x_word ^ y_word) & mask_word);
645 });
646}
647
648fn highest_power_of_2_dividing(n: NonZeroUsize) -> u32 {
649 1 << n.trailing_zeros()
650}
651
652#[test]
653fn test_highest_power_of_2_dividing() {
654 let powers_of_2: Vec<_> = (1..=20)
655 .map(|n| highest_power_of_2_dividing(n.try_into().unwrap()))
656 .collect();
657 assert_eq!(
658 powers_of_2,
659 vec![1, 2, 1, 4, 1, 2, 1, 8, 1, 2, 1, 4, 1, 2, 1, 16, 1, 2, 1, 4]
660 );
661}