每日开源项目1——HyperLogLog库
什么是 HyperLogLog?
HyperLogLog 是一种概率算法,用于估算大型数据集中不同元素的数量(基数估算)。它的主要优势是:
- 内存效率极高:无论输入数据大小如何,都使用固定大小的内存
- 高精度:在默认配置下,标准误差约为 0.81%
- 高性能:插入和估算操作都是 O(1) 时间复杂度
- 可合并性:多个 HyperLogLog 实例可以合并,适用于分布式系统
为什么 HyperLogLog 好?
1. 内存使用对比
- 传统方法(Set/Map):需要存储每个唯一元素,内存使用随数据量线性增长
- HyperLogLog:固定内存使用,默认配置仅需 16KB
2. 精度与内存的平衡
- 可配置精度:从 2^4 (16 bytes) 到 2^18 (256 KB)
- 在大多数场景下,默认配置提供足够的精度
3. 分布式友好
- 支持多个 HyperLogLog 实例合并
- 顺序无关性:插入顺序不影响结果
运行 Demo
# 初始化模块
go mod tidy# 运行演示
go run main.go
Demo 功能
实际代码案例已贴在下方,自行运行
1. 基本功能演示
- 创建 HyperLogLog 实例
- 插入元素
- 估算唯一元素数量
- 查看内存使用
2. 精度测试
测试不同数据量下的估算精度,展示误差率随数据量的变化。
3. 性能对比测试
对比 HyperLogLog 与传统 Map 方法在:
- 执行时间
- 内存使用
- 精度表现
4. 合并功能演示
展示如何合并两个 HyperLogLog 实例,适用于分布式计算场景。
5. 不同精度配置测试
比较不同精度配置下的:
- 内存使用
- 估算精度
- 适用场景
核心算法特点
LogLog-Beta 算法
当前实现基于 LogLog-Beta 算法,相比传统 HyperLogLog 有以下改进:
- 动态偏差校正,在所有基数范围内保持一致精度
- 统一的估算方法,简化实现
- 更好的性能表现
稀疏表示
对于较低基数,使用稀疏表示显著减少内存使用,类似 HyperLogLog++ 的优化。
Metro 哈希
使用 Metro 哈希替代 xxhash,提供更好的性能和分布质量。
适用场景
特别适合现代互联网应用中的实时分析、用户行为统计、分布式系统监控等场景。它以极小的内存代价换取了处理海量数据的能力
- 实时分析:统计流数据中的唯一用户、事件或项目
- 分布式系统:合并多个节点的基数估算
- 内存受限环境:精确计算成本过高的应用
- 大规模数据处理:处理无法装入内存的数据集
实战效果
- 精度越高,内存使用越多,但估算越准确
- 默认精度 2^14 (16KB) 在大多数场景下提供良好的平衡,基本上,误差是控制在1%以内
- 对于内存敏感的应用,可以使用较低精度
- 对于高精度要求的应用,可以使用较高精度
【重点!!!】而且对比于传统的map来说,数据如下
数据量:100万个元素,随机生成重复数据
效果图如下

代码示例1
package mainimport ("fmt""math/rand""runtime""strconv""strings""time""github.com/axiomhq/hyperloglog"
)func main() {fmt.Println("=== HyperLogLog Demo ===")fmt.Println()// 1. 基本功能演示basicDemo()fmt.Println()// 2. 精度测试precisionTest()fmt.Println()// 3. 性能对比测试performanceComparison()fmt.Println()// 4. 合并功能演示mergeDemo()fmt.Println()// 5. 不同精度配置测试precisionConfigTest()
}// 基本功能演示
func basicDemo() {fmt.Println("1. 基本功能演示")fmt.Println("================")// 创建默认精度的HyperLogLog (2^14 = 16384 registers)hll := hyperloglog.New()// 插入一些数据elements := []string{"user1", "user2", "user3", "user1", "user4", "user2", "user5"}fmt.Printf("插入元素: %v\n", elements)for _, element := range elements {hll.Insert([]byte(element))}// 估算唯一元素数量estimate := hll.Estimate()fmt.Printf("HyperLogLog 估算的唯一元素数量: %d\n", estimate)fmt.Printf("实际唯一元素数量: %d\n", 5) // user1, user2, user3, user4, user5// 显示内存使用情况(序列化后的大小作为近似)data, _ := hll.MarshalBinary()fmt.Printf("HyperLogLog 序列化大小: %d bytes\n", len(data))
}// 精度测试
func precisionTest() {fmt.Println("2. 精度测试")fmt.Println("===========")// 测试不同数据量下的精度testSizes := []int{100, 1000, 10000, 100000}for _, size := range testSizes {hll := hyperloglog.New()// 插入指定数量的唯一元素for i := 0; i < size; i++ {hll.Insert([]byte(fmt.Sprintf("element_%d", i)))}estimate := hll.Estimate()actualCount := uint64(size)errorRate := (float64(estimate) - float64(actualCount)) / float64(actualCount) * 100fmt.Printf("数据量: %d, 估算值: %d, 实际值: %d, 误差率: %.2f%%\n",size, estimate, actualCount, errorRate)}
}// 性能对比测试
func performanceComparison() {fmt.Println("3. 性能对比测试")fmt.Println("===============")testSize := 1000000 // 100万个元素// HyperLogLog 测试fmt.Println("HyperLogLog 性能测试:")start := time.Now()var m1 runtime.MemStatsruntime.GC()runtime.ReadMemStats(&m1)hll := hyperloglog.New()for i := 0; i < testSize; i++ {// 模拟一些重复数据element := fmt.Sprintf("user_%d", rand.Intn(testSize/2))hll.Insert([]byte(element))}var m2 runtime.MemStatsruntime.ReadMemStats(&m2)hllTime := time.Since(start)hllMemory := m2.Alloc - m1.AllochllEstimate := hll.Estimate()fmt.Printf(" 时间: %v\n", hllTime)fmt.Printf(" 内存使用: %d bytes\n", hllMemory)data, _ := hll.MarshalBinary()fmt.Printf(" HyperLogLog 序列化大小: %d bytes\n", len(data))fmt.Printf(" 估算唯一元素数量: %d\n", hllEstimate)// 传统 map 方法测试fmt.Println("\n传统 Map 方法性能测试:")start = time.Now()runtime.GC()runtime.ReadMemStats(&m1)uniqueMap := make(map[string]bool)rand.Seed(time.Now().UnixNano()) // 重置随机种子以保证相同的数据for i := 0; i < testSize; i++ {element := fmt.Sprintf("user_%d", rand.Intn(testSize/2))uniqueMap[element] = true}runtime.ReadMemStats(&m2)mapTime := time.Since(start)mapMemory := m2.Alloc - m1.AllocactualCount := len(uniqueMap)fmt.Printf(" 时间: %v\n", mapTime)fmt.Printf(" 内存使用: %d bytes\n", mapMemory)fmt.Printf(" 实际唯一元素数量: %d\n", actualCount)// 对比结果fmt.Println("\n性能对比结果:")fmt.Printf(" 时间比较: HyperLogLog 比 Map %s %.2fx\n",func() string {if hllTime < mapTime {return "快"}return "慢"}(), float64(mapTime)/float64(hllTime))fmt.Printf(" 内存比较: HyperLogLog 比 Map 节省 %.2fx 内存\n",float64(mapMemory)/float64(hllMemory))fmt.Printf(" 精度: 误差率 %.2f%%\n",(float64(hllEstimate)-float64(actualCount))/float64(actualCount)*100)
}// 合并功能演示
func mergeDemo() {fmt.Println("4. 合并功能演示")fmt.Println("===============")// 创建两个 HyperLogLog 实例hll1 := hyperloglog.New()hll2 := hyperloglog.New()// 向第一个实例添加数据fmt.Println("HLL1 添加数据: user_1 到 user_1000")for i := 1; i <= 1000; i++ {hll1.Insert([]byte(fmt.Sprintf("user_%d", i)))}// 向第二个实例添加数据(有部分重叠)fmt.Println("HLL2 添加数据: user_500 到 user_1500")for i := 500; i <= 1500; i++ {hll2.Insert([]byte(fmt.Sprintf("user_%d", i)))}fmt.Printf("HLL1 估算数量: %d\n", hll1.Estimate())fmt.Printf("HLL2 估算数量: %d\n", hll2.Estimate())// 合并两个 HyperLogLogerr := hll1.Merge(hll2)if err != nil {fmt.Printf("合并失败: %v\n", err)return}fmt.Printf("合并后 HLL1 估算数量: %d\n", hll1.Estimate())fmt.Printf("实际唯一元素数量: %d (user_1 到 user_1500)\n", 1500)
}// 不同精度配置测试
func precisionConfigTest() {fmt.Println("5. 不同精度配置测试")fmt.Println("==================")// 测试不同的精度配置precisions := []uint8{4, 8, 12, 14, 16, 18}testData := 100000 // 10万个唯一元素fmt.Printf("测试数据量: %d 个唯一元素\n\n", testData)fmt.Printf("%-10s %-15s %-15s %-15s %-10s\n", "精度(2^n)", "寄存器数量", "内存使用(bytes)", "估算值", "误差率(%)")fmt.Println(strings.Repeat("-", 70))for _, precision := range precisions {hll := hyperloglog.New() // 使用默认精度// 插入测试数据for i := 0; i < testData; i++ {hll.Insert([]byte(strconv.Itoa(i)))}estimate := hll.Estimate()errorRate := (float64(estimate) - float64(testData)) / float64(testData) * 100registers := 1 << precisiondata, _ := hll.MarshalBinary()memoryUsage := len(data)fmt.Printf("%-10d %-15d %-15d %-15d %-10.2f\n",precision, registers, memoryUsage, estimate, errorRate)}fmt.Println("\n说明:")fmt.Println("- 精度越高,内存使用越多,但估算越准确")fmt.Println("- 默认精度 2^14 (16KB) 在大多数场景下提供良好的平衡")fmt.Println("- 对于内存敏感的应用,可以使用较低精度")fmt.Println("- 对于高精度要求的应用,可以使用较高精度")
}
参考资料
- Axiom HyperLogLog GitHub
