Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/lib/hyperloglog.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * hyperloglog.c
4
 *    HyperLogLog cardinality estimator
5
 *
6
 * Portions Copyright (c) 2014-2026, PostgreSQL Global Development Group
7
 *
8
 * Based on Hideaki Ohno's C++ implementation.  This is probably not ideally
9
 * suited to estimating the cardinality of very large sets;  in particular, we
10
 * have not attempted to further optimize the implementation as described in
11
 * the Heule, Nunkesser and Hall paper "HyperLogLog in Practice: Algorithmic
12
 * Engineering of a State of The Art Cardinality Estimation Algorithm".
13
 *
14
 * A sparse representation of HyperLogLog state is used, with fixed space
15
 * overhead.
16
 *
17
 * The copyright terms of Ohno's original version (the MIT license) follow.
18
 *
19
 * IDENTIFICATION
20
 *    src/backend/lib/hyperloglog.c
21
 *
22
 *-------------------------------------------------------------------------
23
 */
24
25
/*
26
 * Copyright (c) 2013 Hideaki Ohno <hide.o.j55{at}gmail.com>
27
 *
28
 * Permission is hereby granted, free of charge, to any person obtaining a copy
29
 * of this software and associated documentation files (the 'Software'), to
30
 * deal in the Software without restriction, including without limitation the
31
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
32
 * sell copies of the Software, and to permit persons to whom the Software is
33
 * furnished to do so, subject to the following conditions:
34
 *
35
 * The above copyright notice and this permission notice shall be included in
36
 * all copies or substantial portions of the Software.
37
 *
38
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
43
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
44
 * IN THE SOFTWARE.
45
 */
46
47
#include "postgres.h"
48
49
#include <math.h>
50
51
#include "lib/hyperloglog.h"
52
#include "port/pg_bitutils.h"
53
54
0
#define POW_2_32      (4294967296.0)
55
0
#define NEG_POW_2_32    (-4294967296.0)
56
57
static inline uint8 rho(uint32 x, uint8 b);
58
59
/*
60
 * Initialize HyperLogLog track state, by bit width
61
 *
62
 * bwidth is bit width (so register size will be 2 to the power of bwidth).
63
 * Must be between 4 and 16 inclusive.
64
 */
65
void
66
initHyperLogLog(hyperLogLogState *cState, uint8 bwidth)
67
0
{
68
0
  double    alpha;
69
70
0
  if (bwidth < 4 || bwidth > 16)
71
0
    elog(ERROR, "bit width must be between 4 and 16 inclusive");
72
73
0
  cState->registerWidth = bwidth;
74
0
  cState->nRegisters = (Size) 1 << bwidth;
75
0
  cState->arrSize = sizeof(uint8) * cState->nRegisters + 1;
76
77
  /*
78
   * Initialize hashes array to zero, not negative infinity, per discussion
79
   * of the coupon collector problem in the HyperLogLog paper
80
   */
81
0
  cState->hashesArr = palloc0(cState->arrSize);
82
83
  /*
84
   * "alpha" is a value that for each possible number of registers (m) is
85
   * used to correct a systematic multiplicative bias present in m ^ 2 Z (Z
86
   * is "the indicator function" through which we finally compute E,
87
   * estimated cardinality).
88
   */
89
0
  switch (cState->nRegisters)
90
0
  {
91
0
    case 16:
92
0
      alpha = 0.673;
93
0
      break;
94
0
    case 32:
95
0
      alpha = 0.697;
96
0
      break;
97
0
    case 64:
98
0
      alpha = 0.709;
99
0
      break;
100
0
    default:
101
0
      alpha = 0.7213 / (1.0 + 1.079 / cState->nRegisters);
102
0
  }
103
104
  /*
105
   * Precalculate alpha m ^ 2, later used to generate "raw" HyperLogLog
106
   * estimate E
107
   */
108
0
  cState->alphaMM = alpha * cState->nRegisters * cState->nRegisters;
109
0
}
110
111
/*
112
 * Initialize HyperLogLog track state, by error rate
113
 *
114
 * Instead of specifying bwidth (number of bits used for addressing the
115
 * register), this method allows sizing the counter for particular error
116
 * rate using a simple formula from the paper:
117
 *
118
 *   e = 1.04 / sqrt(m)
119
 *
120
 * where 'm' is the number of registers, i.e. (2^bwidth). The method
121
 * finds the lowest bwidth with 'e' below the requested error rate, and
122
 * then uses it to initialize the counter.
123
 *
124
 * As bwidth has to be between 4 and 16, the worst possible error rate
125
 * is between ~25% (bwidth=4) and 0.4% (bwidth=16).
126
 */
127
void
128
initHyperLogLogError(hyperLogLogState *cState, double error)
129
0
{
130
0
  uint8   bwidth = 4;
131
132
0
  while (bwidth < 16)
133
0
  {
134
0
    double    m = (Size) 1 << bwidth;
135
136
0
    if (1.04 / sqrt(m) < error)
137
0
      break;
138
0
    bwidth++;
139
0
  }
140
141
0
  initHyperLogLog(cState, bwidth);
142
0
}
143
144
/*
145
 * Free HyperLogLog track state
146
 *
147
 * Releases allocated resources, but not the state itself (in case it's not
148
 * allocated by palloc).
149
 */
150
void
151
freeHyperLogLog(hyperLogLogState *cState)
152
0
{
153
0
  Assert(cState->hashesArr != NULL);
154
0
  pfree(cState->hashesArr);
155
0
}
156
157
/*
158
 * Adds element to the estimator, from caller-supplied hash.
159
 *
160
 * It is critical that the hash value passed be an actual hash value, typically
161
 * generated using hash_bytes().  The algorithm relies on a specific bit-pattern
162
 * observable in conjunction with stochastic averaging.  There must be a
163
 * uniform distribution of bits in hash values for each distinct original value
164
 * observed.
165
 */
166
void
167
addHyperLogLog(hyperLogLogState *cState, uint32 hash)
168
0
{
169
0
  uint8   count;
170
0
  uint32    index;
171
172
  /* Use the first "k" (registerWidth) bits as a zero based index */
173
0
  index = hash >> (BITS_PER_BYTE * sizeof(uint32) - cState->registerWidth);
174
175
  /* Compute the rank of the remaining 32 - "k" (registerWidth) bits */
176
0
  count = rho(hash << cState->registerWidth,
177
0
        BITS_PER_BYTE * sizeof(uint32) - cState->registerWidth);
178
179
0
  cState->hashesArr[index] = Max(count, cState->hashesArr[index]);
180
0
}
181
182
/*
183
 * Estimates cardinality, based on elements added so far
184
 */
185
double
186
estimateHyperLogLog(hyperLogLogState *cState)
187
0
{
188
0
  double    result;
189
0
  double    sum = 0.0;
190
191
0
  for (Size i = 0; i < cState->nRegisters; i++)
192
0
  {
193
0
    sum += 1.0 / pow(2.0, cState->hashesArr[i]);
194
0
  }
195
196
  /* result set to "raw" HyperLogLog estimate (E in the HyperLogLog paper) */
197
0
  result = cState->alphaMM / sum;
198
199
0
  if (result <= (5.0 / 2.0) * cState->nRegisters)
200
0
  {
201
    /* Small range correction */
202
0
    int     zero_count = 0;
203
204
0
    for (Size i = 0; i < cState->nRegisters; i++)
205
0
    {
206
0
      if (cState->hashesArr[i] == 0)
207
0
        zero_count++;
208
0
    }
209
210
0
    if (zero_count != 0)
211
0
      result = cState->nRegisters * log((double) cState->nRegisters /
212
0
                        zero_count);
213
0
  }
214
0
  else if (result > (1.0 / 30.0) * POW_2_32)
215
0
  {
216
    /* Large range correction */
217
0
    result = NEG_POW_2_32 * log(1.0 - (result / POW_2_32));
218
0
  }
219
220
0
  return result;
221
0
}
222
223
/*
224
 * Worker for addHyperLogLog().
225
 *
226
 * Calculates the position of the first set bit in first b bits of x argument
227
 * starting from the first, reading from most significant to least significant
228
 * bits.
229
 *
230
 * Example (when considering first 10 bits of x):
231
 *
232
 * rho(x = 0b1000000000)   returns 1
233
 * rho(x = 0b0010000000)   returns 3
234
 * rho(x = 0b0000000000)   returns b + 1
235
 *
236
 * "The binary address determined by the first b bits of x"
237
 *
238
 * Return value "j" used to index bit pattern to watch.
239
 */
240
static inline uint8
241
rho(uint32 x, uint8 b)
242
0
{
243
0
  uint8   j = 1;
244
245
0
  if (x == 0)
246
0
    return b + 1;
247
248
0
  j = 32 - pg_leftmost_one_pos32(x);
249
250
0
  if (j > b)
251
0
    return b + 1;
252
253
0
  return j;
254
0
}