datasketches-cpp
Loading...
Searching...
No Matches
HllArray-internal.hpp
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#ifndef _HLLARRAY_INTERNAL_HPP_
21#define _HLLARRAY_INTERNAL_HPP_
22
23#include "HllArray.hpp"
24#include "fdlibm_log.hpp"
25#include "HllUtil.hpp"
26#include "HarmonicNumbers.hpp"
27#include "CubicInterpolation.hpp"
28#include "CompositeInterpolationXTable.hpp"
29#include "CouponList.hpp"
30#include "inv_pow2_table.hpp"
31#include <cstring>
32#include <cmath>
33#include <stdexcept>
34#include <string>
35
36namespace datasketches {
37
38template<typename A>
39HllArray<A>::HllArray(uint8_t lgConfigK, target_hll_type tgtHllType, const A& allocator):
40HllSketchImpl<A>(lgConfigK, tgtHllType, hll_mode::HLL),
41hipAccum_(0.0),
42kxq0_(1 << lgConfigK),
43kxq1_(0.0),
44hllByteArr_(allocator),
45curMin_(0),
46numAtCurMin_(1 << lgConfigK),
47oooFlag_(false),
48rebuild_kxq_curmin_(false)
49{}
50
51template<typename A>
52HllArray<A>::HllArray(const HllArray& other, target_hll_type tgtHllType) :
53 HllSketchImpl<A>(other.getLgConfigK(), tgtHllType, hll_mode::HLL),
54 // remaining fields are initialized to empty sketch defaults
55 // and left to subclass constructor to populate
56 hipAccum_(0.0),
57 kxq0_(1 << other.getLgConfigK()),
58 kxq1_(0.0),
59 hllByteArr_(other.getAllocator()),
60 curMin_(0),
61 numAtCurMin_(1 << other.getLgConfigK()),
62 oooFlag_(false),
63 rebuild_kxq_curmin_(false)
64{}
65
66template<typename A>
67HllArray<A>* HllArray<A>::copyAs(target_hll_type tgtHllType) const {
68 // we may need to recompute KxQ and curMin data for a union gadget,
69 // so only use a direct copy if we have a valid sketch
70 if (tgtHllType == this->getTgtHllType() && !this->isRebuildKxqCurminFlag()) {
71 return static_cast<HllArray*>(copy());
72 }
73
74 // the factory methods replay the coupons and will always rebuild
75 // the sketch in a consistent way
76 switch (tgtHllType) {
77 case target_hll_type::HLL_4:
78 return HllSketchImplFactory<A>::convertToHll4(*this);
79 case target_hll_type::HLL_6:
80 return HllSketchImplFactory<A>::convertToHll6(*this);
81 case target_hll_type::HLL_8:
82 return HllSketchImplFactory<A>::convertToHll8(*this);
83 default:
84 throw std::invalid_argument("Invalid target HLL type");
85 }
86}
87
88template<typename A>
89HllArray<A>* HllArray<A>::newHll(const void* bytes, size_t len, const A& allocator) {
90 if (len < hll_constants::HLL_BYTE_ARR_START) {
91 throw std::out_of_range("Input data length insufficient to hold HLL array");
92 }
93
94 const uint8_t* data = static_cast<const uint8_t*>(bytes);
95 if (data[hll_constants::PREAMBLE_INTS_BYTE] != hll_constants::HLL_PREINTS) {
96 throw std::invalid_argument("Incorrect number of preInts in input stream");
97 }
98 if (data[hll_constants::SER_VER_BYTE] != hll_constants::SER_VER) {
99 throw std::invalid_argument("Wrong ser ver in input stream");
100 }
101 if (data[hll_constants::FAMILY_BYTE] != hll_constants::FAMILY_ID) {
102 throw std::invalid_argument("Input array is not an HLL sketch");
103 }
104
105 const hll_mode mode = HllSketchImpl<A>::extractCurMode(data[hll_constants::MODE_BYTE]);
106 if (mode != HLL) {
107 throw std::invalid_argument("Calling HLL array constructor with non-HLL mode data");
108 }
109
110 const target_hll_type tgtHllType = HllSketchImpl<A>::extractTgtHllType(data[hll_constants::MODE_BYTE]);
111 const bool oooFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::OUT_OF_ORDER_FLAG_MASK) ? true : false);
112 const bool comapctFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::COMPACT_FLAG_MASK) ? true : false);
113
114 const uint8_t lgK = data[hll_constants::LG_K_BYTE];
115 const uint8_t curMin = data[hll_constants::HLL_CUR_MIN_BYTE];
116
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");
120 }
121
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));
126
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));
130
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;
134 if (auxCount > 0) { // necessarily TgtHllType == HLL_4
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());
140 }
141
142 HllArray<A>* sketch = HllSketchImplFactory<A>::newHll(lgK, tgtHllType, 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);
149
150 std::memcpy(sketch->hllByteArr_.data(), data + hll_constants::HLL_BYTE_ARR_START, arrayBytes);
151
152 if (auxHashMap != nullptr) {
153 ((Hll4Array<A>*)sketch)->putAuxHashMap(auxHashMap);
154 }
155
156 aux_ptr.release();
157 return sketch;
158}
159
160template<typename A>
161HllArray<A>* HllArray<A>::newHll(std::istream& is, const A& allocator) {
162 uint8_t listHeader[8];
163 read(is, listHeader, 8 * sizeof(uint8_t));
164
165 if (listHeader[hll_constants::PREAMBLE_INTS_BYTE] != hll_constants::HLL_PREINTS) {
166 throw std::invalid_argument("Incorrect number of preInts in input stream");
167 }
168 if (listHeader[hll_constants::SER_VER_BYTE] != hll_constants::SER_VER) {
169 throw std::invalid_argument("Wrong ser ver in input stream");
170 }
171 if (listHeader[hll_constants::FAMILY_BYTE] != hll_constants::FAMILY_ID) {
172 throw std::invalid_argument("Input stream is not an HLL sketch");
173 }
174
175 hll_mode mode = HllSketchImpl<A>::extractCurMode(listHeader[hll_constants::MODE_BYTE]);
176 if (mode != HLL) {
177 throw std::invalid_argument("Calling HLL constructor with non-HLL mode data");
178 }
179
180 const target_hll_type tgtHllType = HllSketchImpl<A>::extractTgtHllType(listHeader[hll_constants::MODE_BYTE]);
181 const bool oooFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::OUT_OF_ORDER_FLAG_MASK) ? true : false);
182 const bool comapctFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::COMPACT_FLAG_MASK) ? true : false);
183
184 const uint8_t lgK = listHeader[hll_constants::LG_K_BYTE];
185 const uint8_t curMin = listHeader[hll_constants::HLL_CUR_MIN_BYTE];
186
187 HllArray* sketch = HllSketchImplFactory<A>::newHll(lgK, tgtHllType, 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);
192
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);
199
200 const auto numAtCurMin = read<uint32_t>(is);
201 const auto auxCount = read<uint32_t>(is);
202 sketch->putNumAtCurMin(numAtCurMin);
203
204 read(is, sketch->hllByteArr_.data(), sketch->getHllByteArrBytes());
205
206 if (auxCount > 0) { // necessarily TgtHllType == HLL_4
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);
210 }
211
212 if (!is.good()) { throw std::runtime_error("error reading from std::istream"); }
213
214 return sketch_ptr.release();
215}
216
217template<typename A>
218auto HllArray<A>::serialize(bool compact, unsigned header_size_bytes) const -> vector_bytes {
219 const size_t sketchSizeBytes = (compact ? getCompactSerializationBytes() : getUpdatableSerializationBytes()) + header_size_bytes;
220 vector_bytes byteArr(sketchSizeBytes, 0, getAllocator());
221 uint8_t* bytes = byteArr.data() + header_size_bytes;
222 AuxHashMap<A>* auxHashMap = getAuxHashMap();
223
224 bytes[hll_constants::PREAMBLE_INTS_BYTE] = static_cast<uint8_t>(getPreInts());
225 bytes[hll_constants::SER_VER_BYTE] = static_cast<uint8_t>(hll_constants::SER_VER);
226 bytes[hll_constants::FAMILY_BYTE] = static_cast<uint8_t>(hll_constants::FAMILY_ID);
227 bytes[hll_constants::LG_K_BYTE] = static_cast<uint8_t>(this->lgConfigK_);
228 bytes[hll_constants::LG_ARR_BYTE] = static_cast<uint8_t>(auxHashMap == nullptr ? 0 : auxHashMap->getLgAuxArrInts());
229 bytes[hll_constants::FLAGS_BYTE] = this->makeFlagsByte(compact);
230 bytes[hll_constants::HLL_CUR_MIN_BYTE] = static_cast<uint8_t>(curMin_);
231 bytes[hll_constants::MODE_BYTE] = this->makeModeByte();
232
233 std::memcpy(bytes + hll_constants::HIP_ACCUM_DOUBLE, &hipAccum_, sizeof(double));
234 std::memcpy(bytes + hll_constants::KXQ0_DOUBLE, &kxq0_, sizeof(double));
235 std::memcpy(bytes + hll_constants::KXQ1_DOUBLE, &kxq1_, sizeof(double));
236 std::memcpy(bytes + hll_constants::CUR_MIN_COUNT_INT, &numAtCurMin_, sizeof(uint32_t));
237 const uint32_t auxCount = (auxHashMap == nullptr ? 0 : auxHashMap->getAuxCount());
238 std::memcpy(bytes + hll_constants::AUX_COUNT_INT, &auxCount, sizeof(uint32_t));
239
240 const uint32_t hllByteArrBytes = getHllByteArrBytes();
241 std::memcpy(bytes + getMemDataStart(), hllByteArr_.data(), hllByteArrBytes);
242
243 // aux map if HLL_4
244 if (this->tgtHllType_ == HLL_4) {
245 bytes += getMemDataStart() + hllByteArrBytes; // start of auxHashMap
246 if (auxHashMap != nullptr) {
247 if (compact) {
248 for (const uint32_t coupon: *auxHashMap) {
249 std::memcpy(bytes, &coupon, sizeof(coupon));
250 bytes += sizeof(coupon);
251 }
252 } else {
253 std::memcpy(bytes, auxHashMap->getAuxIntArr(), auxHashMap->getUpdatableSizeBytes());
254 }
255 } else if (!compact) {
256 // if updatable, we write even if currently unused so the binary can be wrapped
257 uint32_t auxBytes = 4 << hll_constants::LG_AUX_ARR_INTS[this->lgConfigK_];
258 std::fill_n(bytes, auxBytes, static_cast<uint8_t>(0));
259 }
260 }
261
262 return byteArr;
263}
264
265template<typename A>
266void HllArray<A>::serialize(std::ostream& os, bool compact) const {
267 // header
268 const uint8_t preInts = getPreInts();
269 write(os, preInts);
270 const uint8_t serialVersion = hll_constants::SER_VER;
271 write(os, serialVersion);
272 const uint8_t familyId = hll_constants::FAMILY_ID;
273 write(os, familyId);
274 const uint8_t lgKByte = this->lgConfigK_;
275 write(os, lgKByte);
276
277 AuxHashMap<A>* auxHashMap = getAuxHashMap();
278 uint8_t lgArrByte = 0;
279 if (auxHashMap != nullptr) {
280 lgArrByte = auxHashMap->getLgAuxArrInts();
281 }
282 write(os, lgArrByte);
283
284 const uint8_t flagsByte = this->makeFlagsByte(compact);
285 write(os, flagsByte);
286 write(os, curMin_);
287 const uint8_t modeByte = this->makeModeByte();
288 write(os, modeByte);
289
290 // estimator data
291 write(os, hipAccum_);
292 write(os, kxq0_);
293 write(os, kxq1_);
294
295 // array data
296 write(os, numAtCurMin_);
297
298 const uint32_t auxCount = (auxHashMap == nullptr ? 0 : auxHashMap->getAuxCount());
299 write(os, auxCount);
300 write(os, hllByteArr_.data(), getHllByteArrBytes());
301
302 // aux map if HLL_4
303 if (this->tgtHllType_ == HLL_4) {
304 if (auxHashMap != nullptr) {
305 if (compact) {
306 for (const uint32_t coupon: *auxHashMap) {
307 write(os, coupon);
308 }
309 } else {
310 write(os, auxHashMap->getAuxIntArr(), auxHashMap->getUpdatableSizeBytes());
311 }
312 } else if (!compact) {
313 // if updatable, we write even if currently unused so the binary can be wrapped
314 uint32_t auxBytes = 4 << hll_constants::LG_AUX_ARR_INTS[this->lgConfigK_];
315 std::fill_n(std::ostreambuf_iterator<char>(os), auxBytes, static_cast<char>(0));
316 }
317 }
318}
319
320template<typename A>
321double HllArray<A>::getEstimate() const {
322 if (oooFlag_) {
323 return getCompositeEstimate();
324 }
325 return hipAccum_;
326}
327
328// HLL UPPER AND LOWER BOUNDS
329
330/*
331 * The upper and lower bounds are not symmetric and thus are treated slightly differently.
332 * For the lower bound, when the unique count is <= k, LB >= numNonZeros, where
333 * numNonZeros = k - numAtCurMin AND curMin == 0.
334 *
335 * For HLL6 and HLL8, curMin is always 0 and numAtCurMin is initialized to k and is decremented
336 * down for each valid update until it reaches 0, where it stays. Thus, for these two
337 * isomorphs, when numAtCurMin = 0, means the true curMin is > 0 and the unique count must be
338 * greater than k.
339 *
340 * HLL4 always maintains both curMin and numAtCurMin dynamically. Nonetheless, the rules for
341 * the very small values <= k where curMin = 0 still apply.
342 */
343template<typename A>
344double HllArray<A>::getLowerBound(uint8_t numStdDev) const {
345 HllUtil<A>::checkNumStdDev(numStdDev);
346 const uint32_t configK = 1 << this->lgConfigK_;
347 const double numNonZeros = ((curMin_ == 0) ? (configK - numAtCurMin_) : configK);
348 const double relErr = HllUtil<A>::getRelErr(false, this->oooFlag_, this->lgConfigK_, numStdDev);
349 return fmax(getEstimate() / (1.0 + relErr), numNonZeros);
350}
351
352template<typename A>
353double HllArray<A>::getUpperBound(uint8_t numStdDev) const {
354 HllUtil<A>::checkNumStdDev(numStdDev);
355 const double relErr = HllUtil<A>::getRelErr(true, this->oooFlag_, this->lgConfigK_, numStdDev);
356 return getEstimate() / (1.0 + relErr);
357}
358
364// Original C: again-two-registers.c hhb_get_composite_estimate L1489
365template<typename A>
366double HllArray<A>::getCompositeEstimate() const {
367 const double rawEst = getHllRawEstimate();
368
369 const double* xArr = CompositeInterpolationXTable<A>::get_x_arr(this->lgConfigK_);
370 const uint32_t xArrLen = CompositeInterpolationXTable<A>::get_x_arr_length();
371 const double yStride = CompositeInterpolationXTable<A>::get_y_stride(this->lgConfigK_);
372
373 if (rawEst < xArr[0]) {
374 return 0;
375 }
376
377 const uint32_t xArrLenM1 = xArrLen - 1;
378
379 if (rawEst > xArr[xArrLenM1]) {
380 const double finalY = yStride * xArrLenM1;
381 const double factor = finalY / xArr[xArrLenM1];
382 return rawEst * factor;
383 }
384
385 double adjEst = CubicInterpolation<A>::usingXArrAndYStride(xArr, xArrLen, yStride, rawEst);
386
387 // We need to completely avoid the linear_counting estimator if it might have a crazy value.
388 // Empirical evidence suggests that the threshold 3*k will keep us safe if 2^4 <= k <= 2^21.
389
390 if (adjEst > (3 << this->lgConfigK_)) { return adjEst; }
391
392 const double linEst = getHllBitMapEstimate();
393
394 // Bias is created when the value of an estimator is compared with a threshold to decide whether
395 // to use that estimator or a different one.
396 // We conjecture that less bias is created when the average of the two estimators
397 // is compared with the threshold. Empirical measurements support this conjecture.
398
399 const double avgEst = (adjEst + linEst) / 2.0;
400
401 // The following constants comes from empirical measurements of the crossover point
402 // between the average error of the linear estimator and the adjusted hll estimator
403 double crossOver = 0.64;
404 if (this->lgConfigK_ == 4) { crossOver = 0.718; }
405 else if (this->lgConfigK_ == 5) { crossOver = 0.672; }
406
407 return (avgEst > (crossOver * (1 << this->lgConfigK_))) ? adjEst : linEst;
408}
409
410template<typename A>
411double HllArray<A>::getKxQ0() const {
412 return kxq0_;
413}
414
415template<typename A>
416double HllArray<A>::getKxQ1() const {
417 return kxq1_;
418}
419
420template<typename A>
421double HllArray<A>::getHipAccum() const {
422 return hipAccum_;
423}
424
425template<typename A>
426uint8_t HllArray<A>::getCurMin() const {
427 return curMin_;
428}
429
430template<typename A>
431uint32_t HllArray<A>::getNumAtCurMin() const {
432 return numAtCurMin_;
433}
434
435template<typename A>
436void HllArray<A>::putKxQ0(double kxq0) {
437 kxq0_ = kxq0;
438}
439
440template<typename A>
441void HllArray<A>::putKxQ1(double kxq1) {
442 kxq1_ = kxq1;
443}
444
445template<typename A>
446void HllArray<A>::putHipAccum(double hipAccum) {
447 hipAccum_ = hipAccum;
448}
449
450template<typename A>
451void HllArray<A>::putCurMin(uint8_t curMin) {
452 curMin_ = curMin;
453}
454
455template<typename A>
456void HllArray<A>::putNumAtCurMin(uint32_t numAtCurMin) {
457 numAtCurMin_ = numAtCurMin;
458}
459
460template<typename A>
461bool HllArray<A>::isCompact() const {
462 return false;
463}
464
465template<typename A>
466bool HllArray<A>::isEmpty() const {
467 // mergeHll() is only called with non-empty sketches and sets this flag after updating the
468 // register array. The cached curMin_/numAtCurMin_ may still have their empty-sketch values,
469 // but a pending rebuild therefore proves that this array is not empty.
470 if (rebuild_kxq_curmin_) return false;
471 const uint32_t configK = 1 << this->lgConfigK_;
472 return (curMin_ == 0) && (numAtCurMin_ == configK);
473}
474
475template<typename A>
476void HllArray<A>::putOutOfOrderFlag(bool flag) {
477 oooFlag_ = flag;
478}
479
480template<typename A>
481bool HllArray<A>::isOutOfOrderFlag() const {
482 return oooFlag_;
483}
484
485template<typename A>
486uint32_t HllArray<A>::hllArrBytes(target_hll_type tgtHllType, uint8_t lgConfigK) {
487 switch (tgtHllType) {
488 case HLL_4:
489 return hll4ArrBytes(lgConfigK);
490 case HLL_6:
491 return hll6ArrBytes(lgConfigK);
492 case HLL_8:
493 return hll8ArrBytes(lgConfigK);
494 default:
495 throw std::invalid_argument("Invalid target HLL type");
496 }
497}
498
499template<typename A>
500uint32_t HllArray<A>::hll4ArrBytes(uint8_t lgConfigK) {
501 return 1 << (lgConfigK - 1);
502}
503
504template<typename A>
505uint32_t HllArray<A>::hll6ArrBytes(uint8_t lgConfigK) {
506 const uint32_t numSlots = 1 << lgConfigK;
507 return ((numSlots * 3) >> 2) + 1;
508}
509
510template<typename A>
511uint32_t HllArray<A>::hll8ArrBytes(uint8_t lgConfigK) {
512 return 1 << lgConfigK;
513}
514
515template<typename A>
516uint32_t HllArray<A>::getMemDataStart() const {
517 return hll_constants::HLL_BYTE_ARR_START;
518}
519
520template<typename A>
521uint32_t HllArray<A>::getUpdatableSerializationBytes() const {
522 return hll_constants::HLL_BYTE_ARR_START + getHllByteArrBytes();
523}
524
525template<typename A>
526uint32_t HllArray<A>::getCompactSerializationBytes() const {
527 AuxHashMap<A>* auxHashMap = getAuxHashMap();
528 const uint32_t auxCountBytes = ((auxHashMap == nullptr) ? 0 : auxHashMap->getCompactSizeBytes());
529 return hll_constants::HLL_BYTE_ARR_START + getHllByteArrBytes() + auxCountBytes;
530}
531
532template<typename A>
533uint8_t HllArray<A>::getPreInts() const {
534 return hll_constants::HLL_PREINTS;
535}
536
537template<typename A>
538AuxHashMap<A>* HllArray<A>::getAuxHashMap() const {
539 return nullptr;
540}
541
542template<typename A>
543auto HllArray<A>::getHllArray() const -> const vector_bytes& {
544 return hllByteArr_;
545}
546
547template<typename A>
548void HllArray<A>::hipAndKxQIncrementalUpdate(uint8_t oldValue, uint8_t newValue) {
549 const uint32_t configK = 1 << this->getLgConfigK();
550 // update hip BEFORE updating kxq
551 if (!oooFlag_) { hipAccum_ += configK / (kxq0_ + kxq1_); }
552 // update kxq0 and kxq1; subtract first, then add
553 if (oldValue < 32) { kxq0_ -= INVERSE_POWERS_OF_2[oldValue]; }
554 else { kxq1_ -= INVERSE_POWERS_OF_2[oldValue]; }
555 if (newValue < 32) { kxq0_ += INVERSE_POWERS_OF_2[newValue]; }
556 else { kxq1_ += INVERSE_POWERS_OF_2[newValue]; }
557}
558
564//In C: again-two-registers.c hhb_get_improved_linear_counting_estimate L1274
565template<typename A>
566double HllArray<A>::getHllBitMapEstimate() const {
567 const uint32_t configK = 1 << this->lgConfigK_;
568 const uint32_t numUnhitBuckets = curMin_ == 0 ? numAtCurMin_ : 0;
569
570 //This will eventually go away.
571 if (numUnhitBuckets == 0) {
572 return configK * fdlibm::log(configK / 0.5);
573 }
574
575 const uint32_t numHitBuckets = configK - numUnhitBuckets;
576 return HarmonicNumbers<A>::getBitMapEstimate(configK, numHitBuckets);
577}
578
579//In C: again-two-registers.c hhb_get_raw_estimate L1167
580template<typename A>
581double HllArray<A>::getHllRawEstimate() const {
582 const uint32_t configK = 1 << this->lgConfigK_;
583 double correctionFactor;
584 if (this->lgConfigK_ == 4) { correctionFactor = 0.673; }
585 else if (this->lgConfigK_ == 5) { correctionFactor = 0.697; }
586 else if (this->lgConfigK_ == 6) { correctionFactor = 0.709; }
587 else { correctionFactor = 0.7213 / (1.0 + (1.079 / configK)); }
588 const double hyperEst = (correctionFactor * configK * configK) / (kxq0_ + kxq1_);
589 return hyperEst;
590}
591
592template<typename A>
593void HllArray<A>::setRebuildKxqCurminFlag(bool rebuild) {
594 rebuild_kxq_curmin_ = rebuild;
595}
596
597template<typename A>
598bool HllArray<A>::isRebuildKxqCurminFlag() const {
599 return rebuild_kxq_curmin_;
600}
601
602template<typename A>
603void HllArray<A>::check_rebuild_kxq_cur_min() {
604 if (!rebuild_kxq_curmin_) { return; }
605 // the deferred rebuild is only ever set on an HLL_8 union gadget. Guarding here also keeps
606 // this from ever rewriting curMin_ on an HLL_4 array, whose nibbles are stored relative to it.
607 if (this->getCurMode() != hll_mode::HLL || this->getTgtHllType() != target_hll_type::HLL_8) {
608 rebuild_kxq_curmin_ = false;
609 return;
610 }
611
612 uint32_t num_zeros = 0;
613 double kxq0 = 1 << this->lgConfigK_;
614 double kxq1 = 0;
615
616 auto it = this->begin(true); // want all slots, including the empty ones
617 const auto end = this->end();
618 while (it != end) {
619 uint8_t v = HllUtil<A>::getValue(*it);
620 if (v > 0) {
621 if (v < 32) { kxq0 += INVERSE_POWERS_OF_2[v] - 1.0; }
622 else { kxq1 += INVERSE_POWERS_OF_2[v] - 1.0; }
623 } else {
624 ++num_zeros;
625 }
626 ++it;
627 }
628
629 kxq0_ = kxq0;
630 kxq1_ = kxq1;
631 // HLL_8 convention: curMin is always 0 and numAtCurMin is the number of zero registers.
632 // That is the representation the incremental update path maintains, so the rebuilt state is
633 // indistinguishable from it and the timing of this rebuild is not observable in the image.
634 curMin_ = 0;
635 numAtCurMin_ = num_zeros;
636 rebuild_kxq_curmin_ = false;
637 // HipAccum is not affected
638
639}
640
641template<typename A>
642typename HllArray<A>::const_iterator HllArray<A>::begin(bool all) const {
643 return const_iterator(hllByteArr_.data(), 1 << this->lgConfigK_, 0, this->tgtHllType_, nullptr, 0, all);
644}
645
646template<typename A>
647typename HllArray<A>::const_iterator HllArray<A>::end() const {
648 return const_iterator(hllByteArr_.data(), 1 << this->lgConfigK_, 1 << this->lgConfigK_, this->tgtHllType_, nullptr, 0, false);
649}
650
651template<typename A>
652HllArray<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):
653array_(array), array_size_(array_size), index_(index), hll_type_(hll_type), exceptions_(exceptions), offset_(offset), all_(all)
654{
655 while (index_ < array_size_) {
656 value_ = get_value(array_, index_, hll_type_, exceptions_, offset_);
657 if (all_ || value_ != hll_constants::EMPTY) { break; }
658 ++index_;
659 }
660}
661
662template<typename A>
663typename HllArray<A>::const_iterator& HllArray<A>::const_iterator::operator++() {
664 while (++index_ < array_size_) {
665 value_ = get_value(array_, index_, hll_type_, exceptions_, offset_);
666 if (all_ || value_ != hll_constants::EMPTY) { break; }
667 }
668 return *this;
669}
670
671template<typename A>
672bool HllArray<A>::const_iterator::operator!=(const const_iterator& other) const {
673 return index_ != other.index_;
674}
675
676template<typename A>
677auto HllArray<A>::const_iterator::operator*() const -> reference {
678 return HllUtil<A>::pair(index_, value_);
679}
680
681template<typename A>
682uint8_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) {
683 // TODO: we should be able to improve efficiency here by reading multiple bytes at a time
684 // for HLL4 and HLL6
685 if (hll_type == target_hll_type::HLL_4) {
686 uint8_t value = array[index >> 1];
687 if ((index & 1) > 0) { // odd
688 value >>= 4;
689 } else {
690 value &= hll_constants::loNibbleMask;
691 }
692 if (value == hll_constants::AUX_TOKEN) { // exception
693 return exceptions->mustFindValueFor(index);
694 }
695 return value + offset;
696 } else if (hll_type == target_hll_type::HLL_6) {
697 const size_t start_bit = index * 6;
698 const uint8_t shift = start_bit & 0x7;
699 const size_t byte_idx = start_bit >> 3;
700 const uint16_t two_byte_val = (array[byte_idx + 1] << 8) | array[byte_idx];
701 return (two_byte_val >> shift) & hll_constants::VAL_MASK_6;
702 }
703 // HLL_8
704 return array[index];
705}
706
707template<typename A>
708A HllArray<A>::getAllocator() const {
709 return hllByteArr_.get_allocator();
710}
711
712}
713
714#endif // _HLLARRAY_INTERNAL_HPP_
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