深入浅出HyperLogLog源码,精通统计基数的秘密武器
2023-12-18 10:10:15
HyperLogLog简介
HyperLogLog是一种概率数据结构,它可以以极小的空间代价(常数级空间复杂度),近似估算出一个集合的基数(即元素数量),误差通常在0.81%以内。HyperLogLog在很多场景下都有着广泛的应用,例如:
- 基数统计: HyperLogLog可以用于统计网站的独立访客数、应用程序的活跃用户数、广告点击的独立用户数等。
- 数据去重: HyperLogLog可以用于对数据进行去重处理,例如:过滤重复的IP地址、电子邮件地址等。
- 并集估计: HyperLogLog可以用于估计两个或多个集合的并集基数,而无需合并集合本身。
HyperLogLog原理
HyperLogLog的原理并不复杂,它利用概率学中的概率估计技术来近似估算集合的基数。具体来说,HyperLogLog使用了一个称为“桶”的数组来存储数据。每个桶中存储着若干个哈希值,哈希值是通过对集合中的元素进行哈希计算而得到的。
当一个元素被添加到集合中时,HyperLogLog会计算元素的哈希值,并将哈希值存储在对应的桶中。如果一个元素的哈希值与桶中已有的哈希值冲突,则不进行任何操作。
HyperLogLog的基数估算算法如下:
- 计算所有桶中哈希值的最大值。
- 将最大值除以哈希函数的底数,得到一个近似的基数。
HyperLogLog源码解析
HyperLogLog的源码位于Redis的src/hyperloglog.c
文件中。HyperLogLog的数据结构如下:
struct hyperloglog {
uint8_t *registers;
int64_t length;
uint8_t p;
};
registers
:一个字节数组,用于存储哈希值。length
:数组的长度,即桶的数量。p
:哈希函数的底数。
HyperLogLog的初始化函数如下:
hyperloglog *hyperloglogInit(int64_t length, uint8_t p) {
hyperloglog *llog = zmalloc(sizeof(*llog));
llog->registers = zmalloc(length);
memset(llog->registers, 0, length);
llog->length = length;
llog->p = p;
return llog;
}
该函数首先分配内存空间,然后将数组中的所有元素初始化为0。接着,将数组的长度和哈希函数的底数存储在结构体中。
HyperLogLog的添加元素函数如下:
void hyperloglogAdd(hyperloglog *llog, uint64_t value) {
uint64_t hash = CityHash64(value, llog->p);
int64_t index = hash % llog->length;
llog->registers[index] = MAX(llog->registers[index], __builtin_clzll(hash));
}
该函数首先计算元素的哈希值,然后将哈希值转换为一个索引。接着,将哈希值的最大值存储在对应的桶中。
HyperLogLog的基数估算函数如下:
uint64_t hyperloglogEstimate(hyperloglog *llog) {
uint64_t z = 0;
for (int64_t i = 0; i < llog->length; i++) {
z += llog->registers[i];
}
double alpha = 0.7213 / (1 + 1.079 / llog->length);
double estimate = alpha * llog->length * llog->length / z;
return estimate;
}
该函数首先计算桶中所有哈希值的最大值的总和。接着,计算一个常数alpha,alpha的值与桶的数量有关。最后,将alpha、桶的数量和桶中哈希值的最大值的总和代入公式,计算出基数的近似值。
结语
HyperLogLog是一种非常巧妙的数据结构,它以极小的空间代价实现了集合基数的近似估算。HyperLogLog在很多场景下都有着广泛的应用,例如:网站的独立访客数统计、应用程序的活跃用户数统计、广告点击的独立用户数统计等。
通过对HyperLogLog的原理和源码的分析,我们对HyperLogLog有了更深入的了解。希望本文能够帮助你更好地理解和使用HyperLogLog。