20 #ifndef _HLLARRAY_INTERNAL_HPP_
21 #define _HLLARRAY_INTERNAL_HPP_
23 #include "HllArray.hpp"
24 #include "HllUtil.hpp"
25 #include "HarmonicNumbers.hpp"
26 #include "CubicInterpolation.hpp"
27 #include "CompositeInterpolationXTable.hpp"
28 #include "CouponList.hpp"
29 #include "inv_pow2_table.hpp"
38 HllArray<A>::HllArray(uint8_t lgConfigK,
target_hll_type tgtHllType,
bool startFullSize,
const A& allocator):
39 HllSketchImpl<A>(lgConfigK, tgtHllType, hll_mode::HLL, startFullSize),
41 kxq0_(1 << lgConfigK),
43 hllByteArr_(allocator),
45 numAtCurMin_(1 << lgConfigK),
47 rebuild_kxq_curmin_(false)
51 HllArray<A>::HllArray(
const HllArray& other, target_hll_type tgtHllType) :
52 HllSketchImpl<A>(other.getLgConfigK(), tgtHllType, hll_mode::HLL, other.isStartFullSize()),
56 kxq0_(1 << other.getLgConfigK()),
58 hllByteArr_(other.getAllocator()),
60 numAtCurMin_(1 << other.getLgConfigK()),
62 rebuild_kxq_curmin_(false)
66 HllArray<A>* HllArray<A>::copyAs(target_hll_type tgtHllType)
const {
69 if (tgtHllType == this->getTgtHllType() && !this->isRebuildKxqCurminFlag()) {
70 return static_cast<HllArray*
>(copy());
76 case target_hll_type::HLL_4:
77 return HllSketchImplFactory<A>::convertToHll4(*
this);
78 case target_hll_type::HLL_6:
79 return HllSketchImplFactory<A>::convertToHll6(*
this);
80 case target_hll_type::HLL_8:
81 return HllSketchImplFactory<A>::convertToHll8(*
this);
83 throw std::invalid_argument(
"Invalid target HLL type");
88 HllArray<A>* HllArray<A>::newHll(
const void* bytes,
size_t len,
const A& allocator) {
89 if (len < hll_constants::HLL_BYTE_ARR_START) {
90 throw std::out_of_range(
"Input data length insufficient to hold HLL array");
93 const uint8_t* data =
static_cast<const uint8_t*
>(bytes);
94 if (data[hll_constants::PREAMBLE_INTS_BYTE] != hll_constants::HLL_PREINTS) {
95 throw std::invalid_argument(
"Incorrect number of preInts in input stream");
97 if (data[hll_constants::SER_VER_BYTE] != hll_constants::SER_VER) {
98 throw std::invalid_argument(
"Wrong ser ver in input stream");
100 if (data[hll_constants::FAMILY_BYTE] != hll_constants::FAMILY_ID) {
101 throw std::invalid_argument(
"Input array is not an HLL sketch");
104 const hll_mode mode = HllSketchImpl<A>::extractCurMode(data[hll_constants::MODE_BYTE]);
106 throw std::invalid_argument(
"Calling HLL array constructor with non-HLL mode data");
109 const target_hll_type tgtHllType = HllSketchImpl<A>::extractTgtHllType(data[hll_constants::MODE_BYTE]);
110 const bool oooFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::OUT_OF_ORDER_FLAG_MASK) ?
true :
false);
111 const bool comapctFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::COMPACT_FLAG_MASK) ?
true :
false);
112 const bool startFullSizeFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::FULL_SIZE_FLAG_MASK) ?
true :
false);
114 const uint8_t lgK = data[hll_constants::LG_K_BYTE];
115 const uint8_t curMin = data[hll_constants::HLL_CUR_MIN_BYTE];
117 const uint32_t arrayBytes = hllArrBytes(tgtHllType, lgK);
118 if (len <
static_cast<size_t>(hll_constants::HLL_BYTE_ARR_START + arrayBytes)) {
119 throw std::out_of_range(
"Input array too small to hold sketch image");
122 double hip, kxq0, kxq1;
123 std::memcpy(&hip, data + hll_constants::HIP_ACCUM_DOUBLE,
sizeof(
double));
124 std::memcpy(&kxq0, data + hll_constants::KXQ0_DOUBLE,
sizeof(
double));
125 std::memcpy(&kxq1, data + hll_constants::KXQ1_DOUBLE,
sizeof(
double));
127 uint32_t numAtCurMin, auxCount;
128 std::memcpy(&numAtCurMin, data + hll_constants::CUR_MIN_COUNT_INT,
sizeof(
int));
129 std::memcpy(&auxCount, data + hll_constants::AUX_COUNT_INT,
sizeof(
int));
131 AuxHashMap<A>* auxHashMap =
nullptr;
132 typedef std::unique_ptr<AuxHashMap<A>, std::function<void(AuxHashMap<A>*)>> aux_hash_map_ptr;
133 aux_hash_map_ptr aux_ptr;
135 uint8_t auxLgIntArrSize = data[4];
136 const size_t offset = hll_constants::HLL_BYTE_ARR_START + arrayBytes;
137 const uint8_t* auxDataStart = data + offset;
138 auxHashMap = AuxHashMap<A>::deserialize(auxDataStart, len - offset, lgK, auxCount, auxLgIntArrSize, comapctFlag, allocator);
139 aux_ptr = aux_hash_map_ptr(auxHashMap, auxHashMap->make_deleter());
142 HllArray<A>* sketch = HllSketchImplFactory<A>::newHll(lgK, tgtHllType, startFullSizeFlag, allocator);
143 sketch->putCurMin(curMin);
144 sketch->putOutOfOrderFlag(oooFlag);
145 if (!oooFlag) sketch->putHipAccum(hip);
146 sketch->putKxQ0(kxq0);
147 sketch->putKxQ1(kxq1);
148 sketch->putNumAtCurMin(numAtCurMin);
150 std::memcpy(sketch->hllByteArr_.data(), data + hll_constants::HLL_BYTE_ARR_START, arrayBytes);
152 if (auxHashMap !=
nullptr)
153 ((Hll4Array<A>*)sketch)->putAuxHashMap(auxHashMap);
160 HllArray<A>* HllArray<A>::newHll(std::istream& is,
const A& allocator) {
161 uint8_t listHeader[8];
162 read(is, listHeader, 8 *
sizeof(uint8_t));
164 if (listHeader[hll_constants::PREAMBLE_INTS_BYTE] != hll_constants::HLL_PREINTS) {
165 throw std::invalid_argument(
"Incorrect number of preInts in input stream");
167 if (listHeader[hll_constants::SER_VER_BYTE] != hll_constants::SER_VER) {
168 throw std::invalid_argument(
"Wrong ser ver in input stream");
170 if (listHeader[hll_constants::FAMILY_BYTE] != hll_constants::FAMILY_ID) {
171 throw std::invalid_argument(
"Input stream is not an HLL sketch");
174 hll_mode mode = HllSketchImpl<A>::extractCurMode(listHeader[hll_constants::MODE_BYTE]);
176 throw std::invalid_argument(
"Calling HLL construtor with non-HLL mode data");
179 const target_hll_type tgtHllType = HllSketchImpl<A>::extractTgtHllType(listHeader[hll_constants::MODE_BYTE]);
180 const bool oooFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::OUT_OF_ORDER_FLAG_MASK) ?
true :
false);
181 const bool comapctFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::COMPACT_FLAG_MASK) ?
true :
false);
182 const bool startFullSizeFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::FULL_SIZE_FLAG_MASK) ?
true :
false);
184 const uint8_t lgK = listHeader[hll_constants::LG_K_BYTE];
185 const uint8_t curMin = listHeader[hll_constants::HLL_CUR_MIN_BYTE];
187 HllArray* sketch = HllSketchImplFactory<A>::newHll(lgK, tgtHllType, startFullSizeFlag, allocator);
188 typedef std::unique_ptr<HllArray<A>, std::function<void(HllSketchImpl<A>*)>> hll_array_ptr;
189 hll_array_ptr sketch_ptr(sketch, sketch->get_deleter());
190 sketch->putCurMin(curMin);
191 sketch->putOutOfOrderFlag(oooFlag);
193 const auto hip = read<double>(is);
194 const auto kxq0 = read<double>(is);
195 const auto kxq1 = read<double>(is);
196 if (!oooFlag) sketch->putHipAccum(hip);
197 sketch->putKxQ0(kxq0);
198 sketch->putKxQ1(kxq1);
200 const auto numAtCurMin = read<uint32_t>(is);
201 const auto auxCount = read<uint32_t>(is);
202 sketch->putNumAtCurMin(numAtCurMin);
204 read(is, sketch->hllByteArr_.data(), sketch->getHllByteArrBytes());
207 uint8_t auxLgIntArrSize = listHeader[4];
208 AuxHashMap<A>* auxHashMap = AuxHashMap<A>::deserialize(is, lgK, auxCount, auxLgIntArrSize, comapctFlag, allocator);
209 ((Hll4Array<A>*)sketch)->putAuxHashMap(auxHashMap);
213 throw std::runtime_error(
"error reading from std::istream");
215 return sketch_ptr.release();
219 auto HllArray<A>::serialize(
bool compact,
unsigned header_size_bytes)
const -> vector_bytes {
220 const size_t sketchSizeBytes = (compact ? getCompactSerializationBytes() : getUpdatableSerializationBytes()) + header_size_bytes;
221 vector_bytes byteArr(sketchSizeBytes, 0, getAllocator());
222 uint8_t* bytes = byteArr.data() + header_size_bytes;
223 AuxHashMap<A>* auxHashMap = getAuxHashMap();
225 bytes[hll_constants::PREAMBLE_INTS_BYTE] =
static_cast<uint8_t
>(getPreInts());
226 bytes[hll_constants::SER_VER_BYTE] =
static_cast<uint8_t
>(hll_constants::SER_VER);
227 bytes[hll_constants::FAMILY_BYTE] =
static_cast<uint8_t
>(hll_constants::FAMILY_ID);
228 bytes[hll_constants::LG_K_BYTE] =
static_cast<uint8_t
>(this->lgConfigK_);
229 bytes[hll_constants::LG_ARR_BYTE] =
static_cast<uint8_t
>(auxHashMap ==
nullptr ? 0 : auxHashMap->getLgAuxArrInts());
230 bytes[hll_constants::FLAGS_BYTE] = this->makeFlagsByte(compact);
231 bytes[hll_constants::HLL_CUR_MIN_BYTE] =
static_cast<uint8_t
>(curMin_);
232 bytes[hll_constants::MODE_BYTE] = this->makeModeByte();
234 std::memcpy(bytes + hll_constants::HIP_ACCUM_DOUBLE, &hipAccum_,
sizeof(
double));
235 std::memcpy(bytes + hll_constants::KXQ0_DOUBLE, &kxq0_,
sizeof(
double));
236 std::memcpy(bytes + hll_constants::KXQ1_DOUBLE, &kxq1_,
sizeof(
double));
237 std::memcpy(bytes + hll_constants::CUR_MIN_COUNT_INT, &numAtCurMin_,
sizeof(uint32_t));
238 const uint32_t auxCount = (auxHashMap ==
nullptr ? 0 : auxHashMap->getAuxCount());
239 std::memcpy(bytes + hll_constants::AUX_COUNT_INT, &auxCount,
sizeof(uint32_t));
241 const uint32_t hllByteArrBytes = getHllByteArrBytes();
242 std::memcpy(bytes + getMemDataStart(), hllByteArr_.data(), hllByteArrBytes);
245 if (this->tgtHllType_ == HLL_4) {
246 bytes += getMemDataStart() + hllByteArrBytes;
247 if (auxHashMap !=
nullptr) {
249 for (
const uint32_t coupon: *auxHashMap) {
250 std::memcpy(bytes, &coupon,
sizeof(coupon));
251 bytes +=
sizeof(coupon);
254 std::memcpy(bytes, auxHashMap->getAuxIntArr(), auxHashMap->getUpdatableSizeBytes());
256 }
else if (!compact) {
258 uint32_t auxBytes = 4 << hll_constants::LG_AUX_ARR_INTS[this->lgConfigK_];
259 std::fill_n(bytes, auxBytes,
static_cast<uint8_t
>(0));
267 void HllArray<A>::serialize(std::ostream& os,
bool compact)
const {
269 const uint8_t preInts = getPreInts();
271 const uint8_t serialVersion = hll_constants::SER_VER;
272 write(os, serialVersion);
273 const uint8_t familyId = hll_constants::FAMILY_ID;
275 const uint8_t lgKByte = this->lgConfigK_;
278 AuxHashMap<A>* auxHashMap = getAuxHashMap();
279 uint8_t lgArrByte = 0;
280 if (auxHashMap !=
nullptr) {
281 lgArrByte = auxHashMap->getLgAuxArrInts();
283 write(os, lgArrByte);
285 const uint8_t flagsByte = this->makeFlagsByte(compact);
286 write(os, flagsByte);
288 const uint8_t modeByte = this->makeModeByte();
292 write(os, hipAccum_);
297 write(os, numAtCurMin_);
299 const uint32_t auxCount = (auxHashMap ==
nullptr ? 0 : auxHashMap->getAuxCount());
301 write(os, hllByteArr_.data(), getHllByteArrBytes());
304 if (this->tgtHllType_ == HLL_4) {
305 if (auxHashMap !=
nullptr) {
307 for (
const uint32_t coupon: *auxHashMap) {
311 write(os, auxHashMap->getAuxIntArr(), auxHashMap->getUpdatableSizeBytes());
313 }
else if (!compact) {
315 uint32_t auxBytes = 4 << hll_constants::LG_AUX_ARR_INTS[this->lgConfigK_];
316 std::fill_n(std::ostreambuf_iterator<char>(os), auxBytes,
static_cast<char>(0));
322 double HllArray<A>::getEstimate()
const {
324 return getCompositeEstimate();
345 double HllArray<A>::getLowerBound(uint8_t numStdDev)
const {
346 HllUtil<A>::checkNumStdDev(numStdDev);
347 const uint32_t configK = 1 << this->lgConfigK_;
348 const double numNonZeros = ((curMin_ == 0) ? (configK - numAtCurMin_) : configK);
349 const double relErr = HllUtil<A>::getRelErr(
false, this->oooFlag_, this->lgConfigK_, numStdDev);
350 return fmax(getEstimate() / (1.0 + relErr), numNonZeros);
354 double HllArray<A>::getUpperBound(uint8_t numStdDev)
const {
355 HllUtil<A>::checkNumStdDev(numStdDev);
356 const double relErr = HllUtil<A>::getRelErr(
true, this->oooFlag_, this->lgConfigK_, numStdDev);
357 return getEstimate() / (1.0 + relErr);
367 double HllArray<A>::getCompositeEstimate()
const {
368 const double rawEst = getHllRawEstimate();
370 const double* xArr = CompositeInterpolationXTable<A>::get_x_arr(this->lgConfigK_);
371 const uint32_t xArrLen = CompositeInterpolationXTable<A>::get_x_arr_length();
372 const double yStride = CompositeInterpolationXTable<A>::get_y_stride(this->lgConfigK_);
374 if (rawEst < xArr[0]) {
378 const uint32_t xArrLenM1 = xArrLen - 1;
380 if (rawEst > xArr[xArrLenM1]) {
381 const double finalY = yStride * xArrLenM1;
382 const double factor = finalY / xArr[xArrLenM1];
383 return rawEst * factor;
386 double adjEst = CubicInterpolation<A>::usingXArrAndYStride(xArr, xArrLen, yStride, rawEst);
391 if (adjEst > (3 << this->lgConfigK_)) {
return adjEst; }
393 const double linEst = getHllBitMapEstimate();
400 const double avgEst = (adjEst + linEst) / 2.0;
404 double crossOver = 0.64;
405 if (this->lgConfigK_ == 4) { crossOver = 0.718; }
406 else if (this->lgConfigK_ == 5) { crossOver = 0.672; }
408 return (avgEst > (crossOver * (1 << this->lgConfigK_))) ? adjEst : linEst;
412 double HllArray<A>::getKxQ0()
const {
417 double HllArray<A>::getKxQ1()
const {
422 double HllArray<A>::getHipAccum()
const {
427 uint8_t HllArray<A>::getCurMin()
const {
432 uint32_t HllArray<A>::getNumAtCurMin()
const {
437 void HllArray<A>::putKxQ0(
double kxq0) {
442 void HllArray<A>::putKxQ1(
double kxq1) {
447 void HllArray<A>::putHipAccum(
double hipAccum) {
448 hipAccum_ = hipAccum;
452 void HllArray<A>::putCurMin(uint8_t curMin) {
457 void HllArray<A>::putNumAtCurMin(uint32_t numAtCurMin) {
458 numAtCurMin_ = numAtCurMin;
462 bool HllArray<A>::isCompact()
const {
467 bool HllArray<A>::isEmpty()
const {
468 const uint32_t configK = 1 << this->lgConfigK_;
469 return (curMin_ == 0) && (numAtCurMin_ == configK);
473 void HllArray<A>::putOutOfOrderFlag(
bool flag) {
478 bool HllArray<A>::isOutOfOrderFlag()
const {
483 uint32_t HllArray<A>::hllArrBytes(target_hll_type tgtHllType, uint8_t lgConfigK) {
484 switch (tgtHllType) {
486 return hll4ArrBytes(lgConfigK);
488 return hll6ArrBytes(lgConfigK);
490 return hll8ArrBytes(lgConfigK);
492 throw std::invalid_argument(
"Invalid target HLL type");
497 uint32_t HllArray<A>::hll4ArrBytes(uint8_t lgConfigK) {
498 return 1 << (lgConfigK - 1);
502 uint32_t HllArray<A>::hll6ArrBytes(uint8_t lgConfigK) {
503 const uint32_t numSlots = 1 << lgConfigK;
504 return ((numSlots * 3) >> 2) + 1;
508 uint32_t HllArray<A>::hll8ArrBytes(uint8_t lgConfigK) {
509 return 1 << lgConfigK;
513 uint32_t HllArray<A>::getMemDataStart()
const {
514 return hll_constants::HLL_BYTE_ARR_START;
518 uint32_t HllArray<A>::getUpdatableSerializationBytes()
const {
519 return hll_constants::HLL_BYTE_ARR_START + getHllByteArrBytes();
523 uint32_t HllArray<A>::getCompactSerializationBytes()
const {
524 AuxHashMap<A>* auxHashMap = getAuxHashMap();
525 const uint32_t auxCountBytes = ((auxHashMap ==
nullptr) ? 0 : auxHashMap->getCompactSizeBytes());
526 return hll_constants::HLL_BYTE_ARR_START + getHllByteArrBytes() + auxCountBytes;
530 uint8_t HllArray<A>::getPreInts()
const {
531 return hll_constants::HLL_PREINTS;
535 AuxHashMap<A>* HllArray<A>::getAuxHashMap()
const {
540 auto HllArray<A>::getHllArray() const -> const vector_bytes& {
545 void HllArray<A>::hipAndKxQIncrementalUpdate(uint8_t oldValue, uint8_t newValue) {
546 const uint32_t configK = 1 << this->getLgConfigK();
548 if (!oooFlag_) hipAccum_ += configK / (kxq0_ + kxq1_);
550 if (oldValue < 32) { kxq0_ -= INVERSE_POWERS_OF_2[oldValue]; }
551 else { kxq1_ -= INVERSE_POWERS_OF_2[oldValue]; }
552 if (newValue < 32) { kxq0_ += INVERSE_POWERS_OF_2[newValue]; }
553 else { kxq1_ += INVERSE_POWERS_OF_2[newValue]; }
563 double HllArray<A>::getHllBitMapEstimate()
const {
564 const uint32_t configK = 1 << this->lgConfigK_;
565 const uint32_t numUnhitBuckets = curMin_ == 0 ? numAtCurMin_ : 0;
568 if (numUnhitBuckets == 0) {
569 return configK * log(configK / 0.5);
572 const uint32_t numHitBuckets = configK - numUnhitBuckets;
573 return HarmonicNumbers<A>::getBitMapEstimate(configK, numHitBuckets);
578 double HllArray<A>::getHllRawEstimate()
const {
579 const uint32_t configK = 1 << this->lgConfigK_;
580 double correctionFactor;
581 if (this->lgConfigK_ == 4) { correctionFactor = 0.673; }
582 else if (this->lgConfigK_ == 5) { correctionFactor = 0.697; }
583 else if (this->lgConfigK_ == 6) { correctionFactor = 0.709; }
584 else { correctionFactor = 0.7213 / (1.0 + (1.079 / configK)); }
585 const double hyperEst = (correctionFactor * configK * configK) / (kxq0_ + kxq1_);
590 void HllArray<A>::setRebuildKxqCurminFlag(
bool rebuild) {
591 rebuild_kxq_curmin_ = rebuild;
595 bool HllArray<A>::isRebuildKxqCurminFlag()
const {
596 return rebuild_kxq_curmin_;
600 void HllArray<A>::check_rebuild_kxq_cur_min() {
601 if (!rebuild_kxq_curmin_) {
return; }
603 uint8_t cur_min = 64;
604 uint32_t num_at_cur_min = 0;
605 double kxq0 = 1 << this->lgConfigK_;
608 auto it = this->begin(
true);
609 const auto end = this->end();
611 uint8_t v = HllUtil<A>::getValue(*it);
613 if (v < 32) { kxq0 += INVERSE_POWERS_OF_2[v] - 1.0; }
614 else { kxq1 += INVERSE_POWERS_OF_2[v] - 1.0; }
616 if (v > cur_min) { ++it;
continue; }
629 numAtCurMin_ = num_at_cur_min;
630 rebuild_kxq_curmin_ =
false;
636 typename HllArray<A>::const_iterator HllArray<A>::begin(
bool all)
const {
637 return const_iterator(hllByteArr_.data(), 1 << this->lgConfigK_, 0, this->tgtHllType_,
nullptr, 0, all);
641 typename HllArray<A>::const_iterator HllArray<A>::end()
const {
642 return const_iterator(hllByteArr_.data(), 1 << this->lgConfigK_, 1 << this->lgConfigK_, this->tgtHllType_,
nullptr, 0,
false);
646 HllArray<A>::const_iterator::const_iterator(
const uint8_t* array, uint32_t array_size, uint32_t index, target_hll_type hll_type,
const AuxHashMap<A>* exceptions, uint8_t offset,
bool all):
647 array_(array), array_size_(array_size), index_(index), hll_type_(hll_type), exceptions_(exceptions), offset_(offset), all_(all)
649 while (index_ < array_size_) {
650 value_ = get_value(array_, index_, hll_type_, exceptions_, offset_);
651 if (all_ || value_ != hll_constants::EMPTY)
break;
657 typename HllArray<A>::const_iterator& HllArray<A>::const_iterator::operator++() {
658 while (++index_ < array_size_) {
659 value_ = get_value(array_, index_, hll_type_, exceptions_, offset_);
660 if (all_ || value_ != hll_constants::EMPTY)
break;
666 bool HllArray<A>::const_iterator::operator!=(
const const_iterator& other)
const {
667 return index_ != other.index_;
671 auto HllArray<A>::const_iterator::operator*() const -> reference {
672 return HllUtil<A>::pair(index_, value_);
676 uint8_t HllArray<A>::const_iterator::get_value(
const uint8_t* array, uint32_t index, target_hll_type hll_type,
const AuxHashMap<A>* exceptions, uint8_t offset) {
679 if (hll_type == target_hll_type::HLL_4) {
680 uint8_t value = array[index >> 1];
681 if ((index & 1) > 0) {
684 value &= hll_constants::loNibbleMask;
686 if (value == hll_constants::AUX_TOKEN) {
687 return exceptions->mustFindValueFor(index);
689 return value + offset;
690 }
else if (hll_type == target_hll_type::HLL_6) {
691 const size_t start_bit = index * 6;
692 const uint8_t shift = start_bit & 0x7;
693 const size_t byte_idx = start_bit >> 3;
694 const uint16_t two_byte_val = (array[byte_idx + 1] << 8) | array[byte_idx];
695 return (two_byte_val >> shift) & hll_constants::VAL_MASK_6;
702 A HllArray<A>::getAllocator()
const {
703 return hllByteArr_.get_allocator();
DataSketches namespace.
Definition: binomial_bounds.hpp:38
target_hll_type
Specifies the target type of HLL sketch to be created.
Definition: hll.hpp:72
@ HLL_6
6 bits per entry (fixed size)
Definition: hll.hpp:74
@ HLL_8
8 bits per entry (fastest, fixed size)
Definition: hll.hpp:75
@ HLL_4
4 bits per entry (most compact, size may vary)
Definition: hll.hpp:73