布隆过滤器参数实测:120KB 装 10 万条 URL,k 开到 20 反而更差

举报
deli007 发表于 2026/10/08 09:43:43 2026/10/08
【摘要】 布隆过滤器参数实测:120KB 装 10 万条 URL,k 开到 20 反而更差

布隆过滤器误判率实验室:120KB 位数组插入 10 万条数据后的实测面板

给 10 万条 URL 做去重,布隆过滤器要开多大内存?

我把这个问题做成了一个能自己跑的页面:位数组 120KB(983,040 位)+ 7 个哈希函数,插入 10 万条数据,实测误判率 0.945%,理论值 0.890%,两者只差 6.2%。

同一块 120KB 内存,把哈希个数 k 从 7 加到 20,误判率反而涨到 5.675%;把内存从 120KB 加到 1MB,20,000 次查询一次都没误判——多出来的 0.9MB,大部分场景是用不上的。

这篇只讲三件事:内存开多大、k 取几个、这两件事怎么用实测数据验证。

一、先说结论:10 万条 URL,120KB 就够(附速查表)

布隆过滤器的误判率不由代码写得好不好决定,只由三个数决定:位数组大小 m、哈希函数个数 k、要存多少元素 n。反过来按目标误判率推,结论很干脆:

目标误判率 每条数据分到多少位(m/n) 10 万条 URL 需要的内存 最优哈希个数 k*
10% 4.8 位 59 KB 3
1% 9.6 位 117 KB 7
0.1% 14.4 位 176 KB 10

表里的 k* 有解析解:k* = round((m / n) × ln2),不用试凑;内存按 m / 8 换算成字节。

10 万条 URL 想压在 1% 误判率以内,120KB 左右就够了,大约是一张普通网页配图的大小。而且很多场景根本不需要压到 1%:爬虫 URL 去重、缓存穿透拦截这类用途,1%~10% 都能接受——因为误判的代价只是「多查一次数据库」,不是「答错」。

二、为什么 120KB 能装下 10 万条:一次查询到底发生了什么

布隆过滤器的结构非常朴素:一个长度为 m 的位数组,初始全是 0,外加 k 个哈希函数。

  • 插入一条数据:把这条数据分别喂给 k 个哈希函数,得到 k 个位置,把这 k 位全部置 1。
  • 查询一条数据:同样算出 k 个位置,只要有一位是 0,就可以断定「这条数据一定没插入过」;k 位全是 1,只能说「可能存在」。

它换取的代价就在这里:1 个位只存 1 bit 信息,一条 URL 却要占用 k 个位,所以它天生只能回答「一定不存在 / 可能存在」。好处是省内存——10 万条 URL 原样存下来少说几 MB,压成 120KB 的位图,代价是 1% 的假阳性。

而且这个假阳性是单向的:已插入的数据永远不会被判成「不存在」(没有假阴性),只会把没插入的误判成「可能存在」。所以它适合放在数据库前面当一层挡板:说不存在就直接返回,说可能存在再去数据库核实。

查询一条没插入过的字符串:7 个哈希位置里有 0,直接判定一定不存在

上图是查询一条确定没插入过的字符串「hello」:7 个哈希位置逐个给出「0 还是 1」,第 2 个位置是 0,页面直接判定「一定不存在」。插入数据时则是把这 7 位置 1、橙色标注。

同一张图里还能对上一个数:放大视图只看前 8192 位,其中 4,146 位是 1(50.61%),和全景统计的 50.923% 基本吻合——这是检查可视化有没有画错的最快办法,两边对不上就说明聚合或计数有问题。

三、实测:120KB / k=7 / 10 万条的误判率是 0.945%

把位数组设成 120KB(983,040 位)、k=7、插入 10 万条,页面给出的实测结果:

指标 数值
位数组大小 983,040 位(120KB)
已置 1 的位数 / 占比 500,589 / 50.923%
理论误判率 (1−e^(−kn/m))^k 0.890%
实测误判率(20,000 个未插入样本) 0.945%
实测与理论的相对误差 6.2%

「实测」不是估的:页面用固定种子的伪随机数生成 20,000 条确定没插入过的字符串,逐条查询,统计被判成「可能存在」的比例。同一组参数重复跑,结果完全一致——这是可复现的前提。

6.2% 的偏差来自两点:理论公式假设 k 个哈希完全独立且均匀,实际哈希做不到;另外 20,000 次采样本身也有统计波动(0.945% 对应约 189 次命中,采样标准差约 7%)。所以别把 0.945% 当精确值,它和 0.890% 是同一档。

误判率曲线:横轴是已插入元素数,纵轴是误判率(对数坐标)

把 n 从 1 拉到 10 万,曲线是这个形状:前 1 万条几乎贴着 0(理论上 n=10,000 时只有 7.2×10⁻⁹),最后 10 倍区间才陡然抬头。这解释了为什么「按峰值容量算内存」比「按当前数据量算」重要——容量的账要按一年后的数据量算,但内存早就该按那个数开好。

四、k 不是越大越好:同一块 120KB,k 从 1 调到 20

这是这篇最想让你记住的一张表。位数组固定 120KB、n 固定 10 万,只改 k:

k 已置 1 位占比 理论误判率 实测误判率(20,000 样本)
1 9.668% 9.672% 9.660%
2 18.387% 3.389% 3.300%
4 33.403% 1.249% 1.225%
7 50.923% 0.890% 0.945%
10 63.832% 1.125% 1.150%
14 75.886% 2.117% 2.025%
20 86.930% 6.067% 5.675%

从 k=1 加到 k=7,误判率从 9.660% 一路降到 0.945%;k 继续加到 20,误判率反而回升到 5.675%——比 k=1 只好了不到一倍。所以「多挂几个哈希函数更保险」是个错觉,k 过了最优点就是负收益。

原因不复杂,看第二列就懂了:

  • k 越大,插入一条数据就要置 k 个位,位数组越快被填满。k=20 时 86.9% 的位已经是 1。
  • 查询要求 k 位全为 1 才判「可能存在」。k 变大确实提高了门槛,可位数组同时被自己填得更满。
  • 两个效应相乘:门槛的收益按 k 的指数涨,饱和的代价也按 k 的指数涨,前者先占上风、之后被后者反超,最优解正好落在 k* = (m/n)·ln2。

120KB / 10 万条时 m/n = 9.83,算出来 k* = 6.8,取整就是 7——和实测的最低点一致。这也是速查表里 k 一列的来源。

最优 k 小工具:给定 m/n 比值直接算出 k* 和对应误判率

五、内存也不是越大越好:120KB 和 1MB 差在哪

反过来把 k 固定在最优值、只改内存,对比更明显:

位数组 已置 1 位占比 理论误判率 20,000 次查询的实测
60 KB(491,520 位),k=3 45.680% 9.534% 9.825%
120 KB(983,040 位),k=7 50.923% 0.890% 0.945%
1 MB(8,388,608 位),k=7 8.004% 2.11×10⁻⁶% 0 次命中

从 60KB 加到 120KB,误判率从 9.825% 掉到 0.945%,内存只多花 60KB,这是全场性价比最高的一档。再往上加到 1MB:理论误判率 2.11×10⁻⁶%(大约 4700 万次查询才误判一次),20,000 次采样一次都没命中。

页面对这种情况的处理值得一提:它没有直接报「误判率 0%」,而是标注「低于采样分辨率,实测与理论不可分辨」。20,000 次采样能分辨的下限大约是 0.005%,比这更小的误判率,采样再多次也测不出来——想知道真实值只能算,不能测。这一点在做性能测试时同样成立:样本量决定了你能断言的最小差异。

六、准备环境与实操:进入码道 Web,从一句话需求到 13 项自检

这个页面是用码道做的。码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用的是 WebUI 版。

浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwsf&sourcead=dmzntgwwbwz,登录后就能在对话窗口输入需求,不需要装软件。

我把需求写成了 11 条验收点,核心是这几句:

  • 单文件 index.html,原生 JS,不引入任何外部库、不联网
  • 位数组可视化成方格,插入时点亮命中的位,查询时逐个展示 k 个位置是 0 还是 1
  • m(1KB~4MB)、k(1~20)、n(1~200000)可调
  • 实测误判率用固定种子的伪随机样本统计,并和理论公式 (1−e^(−kn/m))^k 并排显示
  • 画误判率曲线,给出「最优 k」小工具,内置 4 组以上预设
  • 失败路径全部要有中文提示且不能崩
  • 页面里要有自检脚本,把自检结果显示出来

最后一句是这次最值钱的地方:要求它把自检渲染在页面上。交付时页面里有 13 项断言,全部通过,其中包括一条「实测误判率与理论值相对误差 < 15%」的硬断言(自检自己跑的那组是 m=8192、k=7、n=2000、样本 30,000:实测 25.447% vs 理论 24.706%)。

页面内置的 13 项自检,全部通过

13 项里我挑几条能说明它没在敷衍:FNV-1a 可复现性(同参数两次计算位置完全一致)、哈希独立性(同一字符串的 k 个位置去重后仍有 12/12 个不同位置)、无假阴性(已插入的 1,000 个元素逐个查询全部判定「可能存在」)、增量维护的置 1 位数与全量扫描结果一致。这些断言都是「跑给别人看」的,不是「声明自己写完了」。

交付物就是一个单文件 index.html(本地 58 KB),源码里没有 fetch、没有 XMLHttpRequest、没有任何外部 URL,零依赖、零网络请求,直接双击就能打开。

七、验证与踩坑:四个真实问题,以及怎么自己复现

第一版交付后我逐张看了截图,发现三个问题,又让它改了一轮:

  1. 曲线图的坐标轴标题压在图里。Y 轴「误判率」是竖排文字,和刻度数字叠在一起;X 轴标题也压着刻度。这不是功能 bug,但截图放进文章就是明显的瑕疵。
  2. 只画了「当前 k」一条曲线,看不出「k 越大越差」这个结论。第二版改成同时画 k = 1、2、4、7、10、14、20 七条理论曲线并叠加实测点,上面那张曲线图就是这么来的。
  3. 位数组在 m 很大时是一整片同色方块。983,040 位按 820 位一段聚合,段内 50% 左右的置 1 密度全都落在同一个颜色档,看不出结构。第二版把密度色阶拉开才好辨认。

改完第二轮又抓到一个更细的问题:放大视图的说明文字里多了一个写死的「1」,渲染出来是「窗口内置 1 4,146 / 8192」。功能没错,但数字旁边的杂字会让人怀疑统计到底准不准,所以又提了一轮把它改成「窗口内已置 1 的位:4,146 / 8192(50.61%)」。这类问题只有把截图放大看才会发现。

另外两个边界值得记一下:

  • 取回源码不能直接抓预览。码道 Web 的内置预览是一个 blob URL 的 iframe,document 里拿不到内容;真源码要走同源接口按路径取文件,别在 iframe 上折腾。
  • 参数校验的提示写得很实在。n 超过位数组总位数时,页面的原话是:「n 远大于 m:要插入的元素个数(20000)超过了位数组的总位数(8192),每个 1 位平均要承载一个以上元素,位数组会完全饱和、误判率趋近 100%,已取消本次操作。请增大 m 或减小 n(提示:m/n 最好 ≥ 6,工程上常取 m/n ≈ 10~16)。」——把「为什么拒绝」和「改到哪个量级」一起说清楚了,这比「参数错误」四个字有用得多。

想自己复现,不需要装任何东西。

# 把 index.html 放到任意目录,起一个本地静态服务
python -m http.server 8000
# 浏览器打开 http://localhost:8000/index.html

打开后点「自检脚本」区域的「重新运行自检」,13 项断言会重新跑一遍;想看本文的数据,依次选「生产典型」预设(1MB / k=7 / 10 万条),再把 m 调成 120KB、k 依次改 1/2/4/7/10/14/20,每次点「插入 n 个元素」即可。整套跑下来不到三分钟。

八、用码道 Web 做这类小工具的三点体会

  1. 把「验收标准」写进需求,而不是写「你做完就行」。 11 条需求里真正起作用的是第 10 条(自检渲染到页面上)和第 8 条(失败路径要有中文提示)。这两条逼着它把边界情况跑一遍,而不是只交一个能跑通的正常路径。
  2. 要求它自证,而不是自己声明。 「自检 13/13 通过」和「功能已实现」是两种东西。前者我可以点一下按钮复核,后者我只能选择信或不信。
  3. 第一版一定会有看起来没问题、但截图一放大就露馅的细节。 坐标轴压字、色阶不够、图例遮挡,这些都不影响功能,但影响它能不能被贴进文章。所以交付后要逐张看图,再提一轮具体的修改点——「第 1 张图 Y 轴文字压住刻度」比「再优化一下界面」有效一百倍。

九、总结

回到标题:120KB 装 10 万条 URL,k 开到 20 反而更差。三个可以直接拿去用的数:

  • 内存按目标误判率反推:1% 误判率 → 每条 9.6 位;0.1% → 每条 14.4 位;10 万条就是 120KB 和 176KB。
  • k 取 round((m/n)·ln2):120KB / 10 万条是 7。实测 k=1 时 9.66%、k=7 时 0.95%、k=20 时 5.68%,两头都更差。
  • 采样量决定你能断言什么:20,000 次采样只能分辨到 0.005% 左右,比这更小的理论误判率,测出来的只会是 0。

布隆过滤器不复杂,麻烦的从来是「参数怎么定」。把参数、实测、理论公式摆在同一屏上,对着曲线调两次,比背公式快得多。

想自己动手:源码在 Demo Park 公开仓库的 codearts-bloom-filter-lab 下,index.html 双击就能打开,截图与需求原文在同一目录;参数扫描按上一节的步骤跑,不到三分钟。


本文用到的页面由码道生成,源码为单个 index.html,可离线打开;文中所有误判率均为页面实测输出,理论值与独立复算结果一致。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。