一段占总耗时的 p,把它做快 s 倍,整体加速是
1 / ((1−p) + p/s)。让 s 趋于无穷 —— 把这一段做到完全不花时间 ——
最好也只有 1/(1−p)。
占 20% 的那一段,天花板是 1.25×。你在它里面有多聪明, 完全不改变这个数:另外 80% 的时间根本不经过它。
所以该问的从来不是「这段能做多快」,而是「这段最多值多少」 —— 而那只是一次除法,在花掉一个季度之前就能做。
先看前提
最大的一段「查数据库」只占 32.4%,就算把它清零,整体也只有 1.48×。要 2.00×,至少得把 2 段一起做没:查数据库、渲染模板。那不是优化,那是重写 —— 早知道这一点,就不会有人先去啃那一段了。
每一段最多值多少
| 阶段 | 份额 | 做快 2× | 做快 10× | 天花板 | 要整体快 2.00× |
|---|---|---|---|---|---|
| 查数据库 | 32.4% | 1.19× | 1.41× | 1.48× | 做不到 |
| 渲染模板 | 23.9% | 1.14× | 1.27× | 1.31× | 做不到 |
| 序列化 | 19.1% | 1.11× | 1.21× | 1.24× | 做不到 |
| 鉴权 | 13.7% | 1.07× | 1.14× | 1.16× | 做不到 |
| 日志 | 10.9% | 1.06× | 1.11× | 1.12× | 做不到 |
「做不到」不是说很难,是说不存在:那一段清零也到不了这个目标, 因为剩下的时间不经过它。要 2.00×,至少得把这几段一起清零:查数据库、渲染模板。
把「查数据库」做快,收益长什么样
横轴是这一段快了多少倍,纵轴是整体快了多少倍。 虚线是天花板 1.48× —— 曲线一辈子够不着它。 从 1× 做到 2× 拿到 40% 的全部收益; 从 2× 再拼到 10×,工作量大得多,只多拿 46%。 这条曲线的形状就是「优化到什么程度该收手」的答案。
一步一步做下去
每一步把当时最大的那段做快一倍。做完一段,剩下几段的份额会重新分配 —— 所以第二件事该做什么,不是从原来那份 profile 上读出来的。
累计加速,以及每一步各自贡献了多少。后面的步骤越来越不值钱 —— 每做掉一段,下一段在「剩下的时间」里占比虽然变大,但能省的绝对时间在变小。
值得注意的
鉴权(13.7% → 1.14×)、日志(10.9% → 1.11×)。这些不是「以后再说」,是算过之后不必做:十倍是很大的工程,换来的整体收益比一次运气好的重跑还小。
它不知道的事
它不知道哪一段好动。 上面排的是效果,不是代价 —— 代价只有你知道。一段占 40% 但要重写整个存储层,和一段占 15% 但改一行配置, 这里看起来是前者赢。真正的决定是「天花板 ÷ 工作量」,而这一页只给得出前一半。
它假设这几段是串行的。 如果两段本来就并行,做快其中一段可能一点用都没有 —— 另一段还在跑。真并行的系统要看关键路径,不是看份额。
它假设做快一段不会拖慢别段。 加缓存省了查询,但多了失效逻辑; 加线程省了等待,但多了争抢。这里算的是「其它都不动」的那个上界。
贴进来的东西只用来算这一次,不写盘、不记日志、不存任何地方。 整个页面没有一行 JavaScript。