数学重学 - 04 数感与估算

这是 数学重学路线图 阶段一的子页面

04 - 数感与估算

为什么要学这个

写代码之前先”估”一下,能避免很多低级错误

架构设计时,数量级判断直接决定技术选型

日活1万和日活1亿,架构完全不同

安全场景:估算暴力破解的时间成本,决定密码策略

大数据场景:提前估算存储/带宽/计算资源,避免上线后爆炸

费米估算是硅谷面试经典题型,也是日常工程决策的核心能力

核心概念:数量级直觉

10的幂次——人话对照表

| 幂次 | 数值 | 人话 | 计算机语境 |
| 10^0 | 1 | 一 | 1个请求 |
| 10^1 | 10 | 十 | 一个小团队 |
| 10^2 | 100 | 百 | 一个接口的并发连接数 |
| 10^3 | 1,000 | 千 | 1KB、1ms |
| 10^4 | 10,000 | 万 | 小型应用日活 |
| 10^5 | 100,000 | 十万 | 中型应用日活 |
| 10^6 | 1,000,000 | 百万 | 1MB、1秒=10^6微秒 |
| 10^7 | 10,000,000 | 千万 | 大型应用日活 |
| 10^8 | 100,000,000 | 亿 | 中国网民的量级 |
| 10^9 | 1,000,000,000 | 十亿 | 1GB、1秒=10^9纳秒 |
| 10^10 | 100亿 | 百亿 | 全球人口量级 |
| 10^12 | 1,000,000,000,000 | 万亿 | 1TB |
| 10^15 | … | 千万亿 | 1PB |

Jeff Dean 延迟数字表(简化版)

每个程序员都应该知道的数字,帮助你在设计系统时快速判断瓶颈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+--------------------------------------+----------------+----------+
| 操作 | 延迟 | 数量级 |
+--------------------------------------+----------------+----------+
| L1 缓存引用 | 0.5 ns | ~1 ns |
| L2 缓存引用 | 7 ns | ~10 ns |
| 主内存引用 | 100 ns | ~100 ns |
| SSD 随机读 | 150 μs | ~100 μs |
| 机械硬盘随机读 | 10 ms | ~10 ms |
| 同机房网络往返 | 0.5 ms | ~1 ms |
| 跨机房网络往返 | ~数十 ms | ~10 ms |
| 从 SSD 顺序读 1MB | 1 ms | ~1 ms |
| 从机械硬盘顺序读 1MB | 20 ms | ~10 ms |
| 跨大洲网络往返 | 150 ms | ~100 ms |
+--------------------------------------+----------------+----------+

关键直觉

内存比 SSD 快 1000倍(100ns vs 100μs)

SSD 比机械硬盘快 100倍(100μs vs 10ms)

本地操作比跨洲网络快 100万倍(100ns vs 100ms)

一条经验法则:每多一层网络/IO,延迟增加2-3个数量级

1ms vs 1μs vs 1ns

1 秒 = 1,000 毫秒(ms)

1 毫秒 = 1,000 微秒(μs)

1 微秒 = 1,000 纳秒(ns)

所以 1秒 = 10^9 纳秒

类比:如果 1ns 是一步(0.5米),那么 1ms 就是从北京走到天津(约100公里),1秒就是绕地球两圈半

费米估算方法

什么是费米估算

以物理学家恩里科·费米命名

核心能力:在信息极不完整的情况下,快速得出数量级合理的估算

不追求精确,追求”对的数量级

错1倍完全OK,错10倍就需要反思

方法论四步走

第一步:确定上下界

先拍脑袋:答案肯定大于X,肯定小于Y

这一步把搜索空间从”完全未知”缩小到”某个范围”

第二步:拆分因子

把大问题拆成多个可估算的小问题

每个小问题都应该是你能”拍个差不多”的

第三步:逐步估算

分别估算每个因子,然后相乘/相加

注意单位换算!

第四步:交叉验证

用不同的拆解方式再算一遍

两种方式得出同一数量级 → 可信度高

差了10倍以上 → 回去检查假设

关键心法

高估和低估会互相抵消(统计上叫”误差的独立性”)

别怕拍脑袋,怕的是不拍就瞎猜

多练就会越来越准

实际估算练习

练习1:日活100万App的日志数据量

拆解

日活用户:100万 = 10^6

每用户每天平均操作次数:假设50次

每次操作产生的日志条数:假设3条(请求日志+业务日志+响应日志)

每条日志大小:假设平均500字节(0.5KB)

计算

总日志条数 = 10^6 × 50 × 3 = 1.5 × 10^8 条

总数据量 = 1.5 × 10^8 × 0.5KB = 7.5 × 10^7 KB ≈ 75 GB/天

交叉验证:按带宽反推,100万日活高峰期QPS约1万,每条日志0.5KB,高峰期每秒5MB日志,一天按10小时高峰 = 5MB × 36000s ≈ 180GB,量级差不多

结论:大约 数十到上百 GB/天

练习2:暴力破解8位纯数字密码需要多久

拆解

8位纯数字,每位0-9,共 10^8 = 1亿种组合

在线攻击(带网络延迟):假设每次尝试100ms → 10^8 × 0.1s = 10^7 秒 ≈ 115天

离线攻击(本地hash爆破):假设每秒10^9次(GPU)→ 10^8 / 10^9 = 0.1秒

结论

在线攻击:加上频率限制基本不可行

离线攻击:8位纯数字密码形同虚设

这就是为什么要求密码包含大小写+数字+特殊字符:字符集从10扩大到70+,8位就是70^8 ≈ 5.7 × 10^14,GPU也要几天

练习3:Kafka集群存储30天日志需要多大磁盘

拆解

接上面估算:假设日志量 100GB/天

30天 = 3TB

Kafka 默认副本因子3 → 3TB × 3 = 9TB

再加上压缩(通常压缩到30-50%)→ 9TB × 0.4 = 3.6TB

留30%余量 → 约5TB

结论:日活百万级App,Kafka存30天日志大约需要 5-10TB 磁盘

练习4:中国有多少程序员

拆解方式一(从人口出发)

中国总人口14亿

劳动年龄人口约9亿

就业人口约7.5亿

IT行业从业者占比约3% → 2250万

其中程序员(开发岗)约占1/3 → 750万

拆解方式二(从企业出发)

中国软件和信息技术企业约10万家

平均每家50-100个开发 → 500-1000万

再加上非IT企业的IT部门,估计再加200-300万

交叉验证:两种方式都指向 700-1000万 这个量级

结论:中国程序员大约 700-1000万

心算技巧进阶

百分比速算

核心思路:把复杂百分比拆成简单百分比的和

10% = 除以10(最基础)

5% = 10% 的一半

1% = 除以100

15% = 10% + 5%

例:15% 的 240

10% × 240 = 24

5% × 240 = 12

15% × 240 = 24 + 12 = 36

例:23% 的 400

20% × 400 = 80

3% × 400 = 12

23% × 400 = 80 + 12 = 92

交换律技巧:8% 的 50 = 50% 的 8 = 4(哪个好算算哪个)

乘法速算

×25:该数 ÷ 4 × 100

25 × 36 = 36 ÷ 4 × 100 = 9 × 100 = 900

×125:该数 ÷ 8 × 1000

125 × 24 = 24 ÷ 8 × 1000 = 3 × 1000 = 3000

×11:个位不动,相邻两位相加写中间

11 × 72 = 7_(7+2)_2 = 792

接近100的两数相乘

97 × 96 = (97-4) × 100 + 3×4 = 9300 + 12 = 9312

公式:(100-a)(100-b) = (100-a-b)×100 + a×b

数量级速算

2^10 ≈ 10^3(1024 ≈ 1000)

2^20 ≈ 10^6(约100万)

2^32 ≈ 4 × 10^9(约42亿,IPv4地址数)

2^64 ≈ 1.8 × 10^19(long类型最大值量级)

这些换算在系统设计中经常用到

Python 代码

费米估算辅助脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"""
费米估算辅助工具
把拆解的因子输入进去,自动算出最终结果和置信区间
"""

def fermi_estimate(factors: list[dict]) -> dict:
"""
每个 factor 是一个字典:
{
"name": "描述",
"low": 最小估计,
"high": 最大估计,
"best": 最佳估计(可选,默认取几何平均)
}
"""
import math

result_low = 1
result_high = 1
result_best = 1

print("=" * 60)
print("费米估算明细")
print("=" * 60)

for f in factors:
low = f["low"]
high = f["high"]
best = f.get("best", math.sqrt(low * high)) # 几何平均

result_low *= low
result_high *= high
result_best *= best

print(f" {f['name']}")
print(f" 范围: [{low:,.0f} ~ {high:,.0f}], 取值: {best:,.0f}")

print("-" * 60)
print(f"最终估算:")
print(f" 乐观: {result_low:,.0f}")
print(f" 悲观: {result_high:,.0f}")
print(f" 最佳: {result_best:,.0f}")
print(f" 数量级: 10^{math.log10(result_best):.1f}")
print("=" * 60)

return {
"low": result_low,
"high": result_high,
"best": result_best,
}

# 示例:估算日活100万App的日志量(GB/天)
factors = [
{"name": "日活用户数", "low": 800_000, "high": 1_200_000, "best": 1_000_000},
{"name": "每用户每天操作次数", "low": 20, "high": 100, "best": 50},
{"name": "每次操作日志条数", "low": 2, "high": 5, "best": 3},
{"name": "每条日志大小(KB)", "low": 0.2, "high": 1.0, "best": 0.5},
]

result = fermi_estimate(factors)
total_kb = result["best"]
total_gb = total_kb / (1024 * 1024)
print(f"\n日志总量约: {total_gb:.0f} GB/天")

延迟对比可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
Jeff Dean 延迟数字可视化
用对数刻度展示各操作的延迟差异
"""
import matplotlib.pyplot as plt
import numpy as np

# 延迟数据(单位:纳秒)
operations = [
("L1 缓存引用", 0.5),
("L2 缓存引用", 7),
("主内存引用", 100),
("SSD 随机读", 150_000),
("同机房网络往返", 500_000),
("SSD 顺序读 1MB", 1_000_000),
("机械硬盘随机读", 10_000_000),
("机械硬盘顺序读 1MB", 20_000_000),
("跨大洲网络往返", 150_000_000),
]

names = [op[0] for op in operations]
latencies = [op[1] for op in operations]

plt.figure(figsize=(12, 6))
bars = plt.barh(range(len(names)), latencies, color="steelblue")
plt.xscale("log")
plt.yticks(range(len(names)), names, fontsize=10)
plt.xlabel("延迟 (纳秒, 对数刻度)", fontsize=12)
plt.title("系统各层延迟对比(Jeff Dean Numbers)", fontsize=14)

# 在每个柱子旁标注人类可读的延迟
def human_readable_ns(ns):
if ns < 1000:
return f"{ns}ns"
elif ns < 1_000_000:
return f"{ns/1000:.0f}μs"
elif ns < 1_000_000_000:
return f"{ns/1_000_000:.0f}ms"
else:
return f"{ns/1_000_000_000:.1f}s"

for i, (bar, lat) in enumerate(zip(bars, latencies)):
plt.text(lat * 1.5, i, human_readable_ns(lat), va="center", fontsize=9)

plt.tight_layout()
plt.savefig("latency_comparison.png", dpi=150)
plt.show()
print("图表已保存为 latency_comparison.png")

密码暴力破解时间估算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""
估算不同密码策略下暴力破解所需时间
"""

def estimate_crack_time(charset_size: int, length: int,
attempts_per_sec: float) -> str:
"""估算暴力破解时间"""
total = charset_size ** length
seconds = total / attempts_per_sec

if seconds < 60:
return f"{seconds:.2f} 秒"
elif seconds < 3600:
return f"{seconds/60:.1f} 分钟"
elif seconds < 86400:
return f"{seconds/3600:.1f} 小时"
elif seconds < 86400 * 365:
return f"{seconds/86400:.1f} 天"
else:
return f"{seconds/(86400*365):.1f} 年"

# 不同攻击场景的速度
scenarios = {
"在线攻击(带限速)": 10, # 10次/秒
"在线攻击(无限速)": 1000, # 1000次/秒
"离线攻击(CPU)": 1_000_000, # 100万次/秒
"离线攻击(GPU集群)": 10_000_000_000, # 100亿次/秒
}

# 不同密码策略
policies = [
("6位纯数字", 10, 6),
("8位纯数字", 10, 8),
("8位小写字母", 26, 8),
("8位大小写+数字", 62, 8),
("10位大小写+数字+特殊", 95, 10),
("12位大小写+数字+特殊", 95, 12),
]

print(f"{'密码策略':<25} {'组合数':<18} ", end="")
for s in scenarios:
print(f"{s:<20}", end="")
print()
print("-" * 120)

for name, charset, length in policies:
total = charset ** length
print(f"{name:<25} {total:<18,.0f} ", end="")
for speed in scenarios.values():
print(f"{estimate_crack_time(charset, length, speed):<20}", end="")
print()

实际应用场景

安全方向

密码策略制定:计算不同密码复杂度的暴力破解成本

加密算法选择:AES-128 vs AES-256 的密钥空间差异(2^128 vs 2^256)

DDoS 攻击规模评估:1Gbps 攻击 = 每秒约 10万个1KB的包

大数据方向

存储容量规划:先估算数据量,再决定集群规模

ETL 任务耗时估算:数据量 × 单条处理时间

内存需求评估:要做 JOIN 的两张表能不能放进内存

后端方向

系统容量规划:日活 → QPS → 需要多少台机器

缓存大小设计:热数据量 × 单条大小 = Redis 需要多大内存

数据库选型:数据量级决定用 MySQL 还是分库分表还是用 NoSQL

日常生活

排队等候时间估算

旅行行程时间规划

购物时快速心算折扣价

常见误区

误区1:追求精确

费米估算的目标是数量级正确,不是小数点后几位

差2倍完全可以接受,差10倍才需要检讨

误区2:忽略单位换算

最常见的错误!KB/MB/GB之间差1000倍

秒/毫秒/微秒之间也差1000倍

养成习惯:每一步都带单位

误区3:所有因子都取最大值或最小值

应该每个因子独立估算,高低会互相抵消

全取最大 = 极端悲观,全取最小 = 极端乐观

误区4:不做交叉验证

只用一种方式估算,万一某个假设离谱了呢

至少用两种不同角度算一遍

练习题

题目1:估算你所在城市有多少外卖骑手

提示:从订单量反推,或从骑手密度反推

题目2:估算 GitHub 上总共有多少个仓库

提示:从用户数和人均仓库数出发

题目3:一个 P99 延迟为 200ms 的 API,每天处理 1000万次请求,大约有多少次请求超过 200ms

答案:1000万 × 1% = 10万次。是不是比你想象的多?

题目4:估算你公司的服务器每天消耗多少度电

提示:服务器功率(约500W) × 数量 × 24小时 ÷ 1000

题目5:如果把全世界每天产生的数据都存到硬盘上,需要多少块 10TB 的硬盘

提示:全球日数据量约 2.5 EB(2.5 × 10^18 字节)

小结

数感不是天赋,是练出来的

记住关键的锚点数字(人口、延迟表、2的幂次)

费米估算的核心:拆解 + 估算 + 交叉验证

每次做技术决策前,先花5分钟估算一下,能省掉很多弯路

下一节:05-基础统计思维


上一章 目录 下一章
03-比例百分比与增长率 数学重学路线图 05-基础统计思维