nx_sketch_linear_counter.nx
buildroot/runtime/nx_sketch_linear_counter.nx
about
sketch_linear_counter.nx -- Linear Counting (Whang-Vander Zanden 1990).
Pre-HLL cardinality primitive. Simpler than HLL; sometimes more
accurate at small cardinalities (n < m/10). Used by Google's
BigQuery as the small-cardinality fallback.
ALGORITHM:
bitmap of m bits, all zero initially.
add(x): set bit at index = hash(x) % m
estimate(n) = -m * ln(zeros / m)
where zeros = count of unset bits at query time. Derivation: if
items are hashed uniformly to m buckets and we observe `zeros`
empty buckets, then by occupancy theory the true count n satisfies
E[zeros] = m * (1 - 1/m)^n ≈ m * e^(-n/m)
inverting: n ≈ -m * ln(zeros / m).
CAPABILITIES (vs HLL):
- LC: accurate at n < m (uses bitmap directly)
- HLL: accurate at n >> m (uses harmonic mean of register maxima)
Together: BigQuery + others run LC for n < threshold, HLL for n >=.
INTEGER-FIXED-POINT IMPLEMENTATION:
ln(zeros/m) approximated via tabulated values keyed on (zeros*100)/m
(percentage of empty bits, in [0, 100]). Table values are
-ln(p/100) * 1_000_000 in PPM (negative of -log so positive).
MEMORY: m bits = m/8 bytes. For m=8192: 1 KB.
LOSSLESS-LANGUAGE DISCIPLINE:
nx_lc_query returns ApproxI64 with NX_ENV_REL_STDDEV. Standard
error per Whang 1990: sqrt(e^t - t - 1) / t where t = n/m (load).
Tabulated by load class.
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_murmur3.nxnx_sketch_types.nx
imported by: nobody (leaf or entry point)
structs
| 48 | struct LinearCounter |
consts
| 45 | const NX_LC_MIN_BITS: i64 = 64 |
| 46 | const NX_LC_MAX_BITS: i64 = 16777216 // 2 MB cap |
functions
| 57 | func nx_lc_is_pow2(n: i64) -> i64 called by 1: nx_lc_alloc |
| 63 | func nx_lc_alloc(m_bits: i64, seed: i64) -> *LinearCounter |
| 83 | func nx_lc_set_bit(lc: *LinearCounter, bit_idx: i64) -> i64 called by 1: nx_lc_add |
| 90 | func nx_lc_get_bit(lc: *LinearCounter, bit_idx: i64) -> i64 |
| 98 | func nx_lc_add(lc: *LinearCounter, key: *u8, len: i64) -> i64 |
| 110 | func nx_lc_popcount_byte(b: i64) -> i64 called by 1: nx_lc_zeros |
| 120 | func nx_lc_zeros(lc: *LinearCounter) -> i64 |
| 148 | func nx_lc_neg_ln_ppm(zeros_x_100: i64, m: i64) -> i64 called by 1: nx_lc_estimate |
| 173 | func nx_lc_estimate(lc: *LinearCounter) -> i64 |
| 187 | func nx_lc_stddev_rel_ppb(load_pct: i64) -> i64 called by 1: nx_lc_query |
| 199 | func nx_lc_query(lc: *LinearCounter) -> *ApproxI64 |
| 214 | func nx_lc_merge(a: *LinearCounter, b: *LinearCounter) -> *LinearCounter calls 1: nx_lc_alloc |
| 227 | func nx_lc_memory_bytes(lc: *LinearCounter) -> i64 |