速成课 · No. 24
性能这件事,每次出错都是同一个套路:有人猜哪里慢,优化了错的地方,白白堆上了复杂度。该有的纪律恰好相反——靠测量找到真正的 bottleneck,只修那一个,然后再测一遍。搞清楚到底是什么让软件变慢,以及描述它的那些词,「让它更快」就从一句猜测变成了一套方法。
20 个概念 · 每个都有一张图 · 结尾附速查表
性能的第一条规则,恰恰是人人都违反的那条:别相信你对「哪里慢」的直觉。几乎所有白费的优化,都来自跳过了这一步。
你对慢的直觉通常是错的
一个医生凭猜测就开出手术方案,从没做过检查——自信、迅速,却常常在错误的地方动刀。
开发者在「时间花到哪儿去了」这件事上,出了名地爱猜错。你确信很慢的那部分,往往没问题;真正的元凶在你从没怀疑过的地方——一个被调用了上百万次的小函数,一次藏起来的数据库调用。
凭直觉行动,意味着你优化了错的东西,添了复杂度,而程序依旧很慢。第一步从来不是「修它」——而是弄清楚时间到底花在了哪儿。
地图:七组共 20 个概念
一张地铁线路图:先看见完整的线路,再一站一站地坐过去。
这门课的概念分成七组——每章一组。先讲怎么测量。再讲你要的是哪种「快」。然后是 bottleneck 和对付它的经典办法。最后讲如何不被平均值骗,什么时候不该优化,以及怎么把整件事当作一个循环来跑。
怎么读这门课
修车师傅不会随手乱换零件:先上仪器,再修一处,再复测。
每个概念都享受同样的待遇:它的英文名(记住它——文档、profiler 和仪表盘里用的就是这个词)、一幅日常生活的画面、一张图,以及一张卡片,上面有是什么、怎么运作、例子、陷阱和关联。
大想法:性能不是一袋小技巧,而是一套方法。测量 → 找到 bottleneck → 只修那一个 → 再测一遍 → 达到目标就停。 课里其余的一切,都是这个循环里的细节。
Profiling — 找出真正的开销
一张逐项列出的账单,一行一行地告诉你钱到底花在了哪儿——于是你不再瞎猜,而是看到吃掉预算的是那一笔,而不是你以为的那些。
Profiler 是一种工具,它测量你的程序到底把时间花在哪儿,细到每个函数。它把「我觉得这个慢」变成「这一个函数占了 80% 的运行时间」。有了它,你修的是真正要紧的东西,而不是你想象出来的东西。
不管是 profiler、计时日志,还是你的**可观测性(observability)**指标,原则都一样:让测量,而不是直觉,把你指向问题。
- 是什么
- 测量程序到底把时间花在哪儿,细到每个函数。
- 怎么运作
- Profiler(或计时日志、指标)记录每个部分的耗时,并给出各自的占比。
- 例子
- 「
getOrders()占了请求的 80%」,而不是「渲染肯定慢」。 - 陷阱
- 在玩具数据上做的 profile 可能指错方向——要在贴近真实的负载下测量。
- 关联
- Measure–Fix–Measure 的第一步;正是它找出 Bottleneck。
Measure–Fix–Measure — 测量、修复、再测量
科学家只改一个变量,重做实验,再检查结果——从不假定改动有效,总是确认它真的有效。
性能是一个循环:先测量找到慢的部分,改它,然后再测一遍,确认这次改动真的有帮助——而不是只把问题挪了个地方,或者让它更糟。
没有第二次测量,你就只是在猜自己到底有没有改善,而这正是「优化」悄悄让东西变慢的原因。把每一次修复都当作一个要验证的假设——和 AI 的 evals、代码的测试是同一种纪律。
- 是什么
- 每一次修复,都用前后两次测量来检验。
- 怎么运作
- 改动前后用同一个测试;一次只改一处,这样你才知道是什么起了作用。
- 例子
- 加了一个索引:查询从 400 ms 降到 40 ms——保留。没变化——撤销。
- 陷阱
- 没有第二次测量,一次「优化」可能悄悄拖慢系统,而没人察觉。
- 关联
- Optimization Loop 的核心;与 evals 和测试是近亲。
对慢的直觉通常是错的。先 profile,找出时间真正花在哪儿,修掉它,再测一遍确认——绝不凭猜测去优化。
优化之前,先弄清你到底要的是哪一种「快」。两个不同的目标总是被混为一谈,而改善其中一个,对另一个可能毫无作用。
Latency — 等一件事的时间
一杯咖啡从下单到端到手里要多久——一位顾客的体验,从头量到尾。
Latency 是一次操作从开始到结束要多久——一个请求、一次页面加载、一条查询。它是单个用户感受到的东西:等待。
当有人说「网站很慢」,他几乎总是指 latency——做了一件事到拿到响应之间的延迟。降低 latency,就是让每一件事本身都发生得更快。
- 是什么
- 一次操作从开始到结束的时间,通常以毫秒计。
- 怎么运作
- 它把路径上的每一步加起来:网络、服务器处理、数据库、渲染。
- 例子
- 从点击到页面就绪——800 ms。
- 陷阱
- 单个 latency 数字通常是平均值,而平均值会藏住慢的情况(第 5 章)。
- 关联
- 与 Throughput 是不同的目标;用 Percentiles 来度量。
Throughput — 每秒能处理多少
整家咖啡馆一小时能出多少杯咖啡——不是一杯要多久,而是整个店里的总流量。
Throughput 是系统在单位时间内处理多少工作——每秒多少个请求,每分钟多少个任务。它说的是总容量,而不是个体的等待。
一个系统可以有很棒的 throughput(同时服务成千上万人),而每个用户仍要等一会儿(latency 高);也可以 latency 很低,但容量有限。它们是不同的目标,你追的是哪一个,决定了你该修什么。
- 是什么
- 单位时间内完成的工作量:每秒请求数,每分钟任务数。
- 怎么运作
- 当系统并行处理得更多,或每个单位花得更少时,它就会上升。
- 例子
- 一个 API 每秒处理 1,000 个请求;一条夜间流水线每小时处理一百万行。
- 陷阱
- 高 throughput 不等于快的体验:每个用户仍可能等很久。
- 关联
- 常常通过 Batching,以牺牲 Latency 为代价来提高。
Batching — 用 latency 换 throughput
摩托车把一个人送到最快(低 latency);公交车一趟运的人最多(高 throughput)。哪种车最好,取决于你要什么。
Latency 和 throughput 是相互独立的,优化其中一个可能伤到另一个。Batching——把工作攒在一起处理——通常会提高 throughput,却增加 latency,因为每一项都要等那一批凑齐。
所以你得决定这里哪个更重要:面向用户的页面,生死取决于 latency;后台的数据流水线,在乎的是 throughput。说清你真正的目标,能让你不去优化那个对用户无关紧要的数字。
- 是什么
- 把许多小操作收集成一个大操作。
- 怎么运作
固定开销(一次网络往返、启动一个任务)按每批付一次,而不是每一项付一次。
- 例子
- 用一条查询插入 1,000 行,而不是 1,000 条单独的查询。
- 陷阱
- 每一项都要等这一批凑满——latency 上升。
- 关联
- 用 Latency 换 Throughput;也是治好 N+1 Query 的同一个思路。
Latency 是等一次操作的时间;throughput 是每秒能处理多少。它们是相互独立的目标——而 batching 往往是用 latency 换 throughput。
性能不是平均分布的。几乎总有一个部分占了大头——在你修好它之前,优化其他任何东西都是白费力气。
Bottleneck — 一个慢的部分说了算
一条链子的强度,恰好等于它最弱的那一环——加固强的环毫无用处;能不能撑住,只由最弱的那一环决定。
在大多数慢的系统里,单独一个 bottleneck 就占掉了大部分时间——一条查询、一个慢服务、一个糟糕的循环。其余的部分本来就已经够快了。
这就是为什么猜测如此浪费:整门手艺就在于找到占大头的开销,而不是去改善那些本来就快的部分。
- 是什么
- 占掉总时间大部分的那一个部分。
- 怎么运作
- Profile 显示各部分的占比;通常有一个部分远大于其余。
- 例子
- 一个页面要 500 ms,其中 400 ms 是对一个没建索引的列做的一条查询。
- 陷阱
- Bottleneck 很少出现在你预期的地方——所以要先 profile。
- 关联
- 用 Profiling 找到;Amdahl's Law 解释了为什么它是该修的东西。
Limiting Step — 系统只能和它最慢的一步一样快
一条一路畅通的高速公路,直到某处封了一条车道,所有车都在龟速爬行——整趟路程的速度由那一个堵点决定,而不是那些畅通的路段。
一个要经过十个步骤的请求,受限于其中最慢的一步——即 limiting step。把另外九个快的步骤再提速,总时间几乎不动;慢的那一步决定了节奏。所以你要专门对准那个堵点。
还要注意:修好一个 bottleneck,常常会暴露出下一个:去掉最慢的一步,新的最慢一步就冒出来了。性能工作,就是不断找到并清除此刻正在限制系统的那个东西。
- 是什么
- 当前决定整条路径速度的那一步。
- 怎么运作
- 去掉一个 limiting step,次慢的那一步就成了新的 limiting step。
- 例子
- 查询快了 10 倍——现在 50 ms 的渲染成了最慢的一步。
- 陷阱
Bottleneck 永远不会彻底消失,只会转移——这就是你需要一个目标来收手的原因。
- 关联
为什么 Optimization Loop 是个循环,以及为什么 Performance Target 是它的出口。
Amdahl's Law — 去优化别处是白费力气
车子爆了胎,你却在给奖杯抛光——力气花在了毫无改变的地方,而真正拦住你的东西被晾在一边。
优化一个只占运行时间 2% 的部分,你最多只能赢得 2%。找到那个占 80% 的部分,修好它就能改变一切。这道算术叫作 Amdahl 定律:你的收益,被你所加速的那部分实际占的时间份额封顶。
把时间花在优化 bottleneck 以外的任何东西上,按定义就是帮不上多大忙的时间。它常常还会把事情变糟,为一个看不见的收益添上复杂度和 bug。测量让你不再满怀爱意地优化自己看得懂的那一部分,而是逼你去处理真正花钱的那一部分。修好 bottleneck,别管其余——直到其余的变成了 bottleneck。
- 是什么
- 给系统的某一部分提速,受限于那一部分所占的时间份额。
- 怎么运作
- 一个占 10% 时间的部分,即使被做到无限快,最多也只能省 10%。
- 例子
- 渲染(10%)快 2 倍:500 → 475 ms。查询(80%)快 2 倍:500 → 300 ms。
- 陷阱
- 优化自己看得懂的东西,感觉很好——但如果它不是 bottleneck,就没有收益。
- 关联
- Bottleneck 背后的数学;反对 Premature Optimization 的论据。
通常是一个 bottleneck 占了大头的时间,而系统只能和它最慢的一步一样快。修好那一个;优化别的任何东西都是白费,直到它变成 bottleneck。
等你真的找到了 bottleneck,修法通常是少数几个经典办法之一。就是这寥寥几个,贡献了现实中绝大多数的提速。
Better Algorithm (Big-O) — 好算法胜过快机器
去同一座城市的两条路:一辆更快的车走又长又绕的路,仍然输给一辆更慢的车走笔直的捷径。路线比发动机更重要。
最大的提速,往往来自更好的算法或数据结构,而不是更快的硬件或微调。Big-O 描述输入变大时时间如何增长。把一个 O(n²) 的循环换成 O(n) 的哈希表查找,可以把几分钟变成几毫秒——再快的机器也比不上这样的改变。
在优化那些小东西之前,先问问是不是思路本身就错了。正如数据结构课所讲的,更好的形态是最便宜、也最大的提速。
- 是什么
- 换一种思路,让工作量随数据增长得更慢。
- 怎么运作
Big-O 显示增长的快慢:O(n²) 是嵌套的「每个跟每个」循环,O(n) 是一遍扫完。
- 例子
- 找出两个列表的公共项:嵌套循环 → 用哈希集合,一遍扫完。
- 陷阱
- 数据小的时候看不出差别——数据一大就爆炸。
- 关联
- Performance by Design 里的主要选择;见数据结构那门课。
N+1 Query — 往返次数太多
为了买东西,一趟只买一样,跑了二十趟商店,而不是拿着清单去一次——吃掉你下午的是路上的奔波,而不是购物本身。
大量的慢,其实是往返次数太多——对数据库或服务反复调用,每次单独看都很便宜,成批累积起来却是灾难。
经典的就是 N+1 查询:先取一个列表,然后对每一项再多发一条查询,把一次往返变成了几百次。修法是批量获取——一次把所有东西都要来。减少往返的次数,通常胜过加快每一次,因为往返本身就是开销。
- 是什么
- 先用一条查询取一个列表,再对每一项各发一条查询。
- 怎么运作
- 每趟去数据库都要付出网络和额外开销;N 趟就把这笔开销乘上 N。
- 例子
100 个订单 → 1 条查询取列表 + 100 条查询取客户。修法:一条带
IN (…)的查询,或者一个 JOIN。- 陷阱
- ORM 会把它藏起来:代码看起来是个简单的循环,底下却是几百条查询。
- 关联
- 用与 Batching 相同的思路治好;常见的 Bottleneck。
Caching — 别把同一件事算两遍
每次有人点菜,都从头重新做一遍,而不是留一锅热着的——最快的活,就是不用重复的活。
Caching——把昂贵的结果记住,这样就不用重新计算——是最大的杠杆之一(它有自己专门的一门课)。
第一次请求走慢路径并把结果存下来;之后的请求直接从内存里拿。代价是内存,以及可能返回过期数据的风险。
- 是什么
- 存下昂贵工作的结果,并再次提供出去。
- 怎么运作
- 查缓存:有——立刻返回(hit);没有——计算、存下、返回(miss)。
- 例子
- 一个目录页要 300 ms 才能构建出来;从缓存里拿,只要几毫秒。
- 陷阱
- 缓存可能返回过期数据;什么时候清除它,本身就是个难题。
- 关联
- Doing Less 的一个特例;在 caching 那门课里有深入讲解。
Doing Less — 最快的活,是你跳过的活
没人点的菜就不做,来了三个人就别摆二十个人的餐桌。
还有一个经典办法——少做事。**惰性(lazily)**地计算,只在真正需要时才算;避免加载你用不上的数据;跳过那些结果最后要被扔掉的工作。
最快的操作,就是你从不运行的那个。往往最好的优化不是把活干得更快,而是压根不干。
- 是什么
- 去掉工作,而不是给它提速。
- 怎么运作
- 惰性计算,只加载你需要的字段,丢掉没人用的结果。
- 例子
从数据库里只取你需要的 3 个字段,而不是全部 50 个;报表等到被打开时才生成。
- 陷阱
- 惰性的工作可能在之后、在一个尴尬的时刻咬你一口——要测量。
- 关联
- 包含 Caching(不重复),并治好一部分 N+1 Query。
大多数提速就是那么几个经典办法:更好的算法、更少的往返(干掉 N+1)、缓存昂贵的结果,以及干脆少做事。最快的活,就是你跳过的活。
你衡量性能的方式,可能会对你撒谎。平均值是最让人安心、也最有误导性的数字,学会越过它去看,正是真正的性能工作与一厢情愿的区别。
Average — 平均值会藏住慢的情况
一个房间里,九个人很舒服,一个人身上着了火,平均温度却很宜人——平均数抹掉了真正要紧的那种情况。
把性能报告成平均值(mean),危险地让人安心:它把快的大多数和慢的少数搅成一个皆大欢喜的数字。可用户感受的不是平均值——每个用户经历的是他自己的那个请求,慢的那些,每一毫秒都感受得到。
平均 200 ms 的响应时间,可能藏着这样一个事实:二十个用户里有一个要等五秒。平均值告诉你系统没问题,而你有真实的一部分用户正在受苦。
- 是什么
- 各次耗时之和除以请求数。
- 怎么运作
- 它把快的和慢的请求混成一个数字。
- 例子
- 「平均是 200 ms」——而二十个请求里有一个要等 5 秒。
- 陷阱
- 看上去体面,而有些用户正在受苦。永远别把目标设在它上面。
- 关联
- 被 Percentiles 取代;它会藏住 Tail Latency。
Percentiles — p50 与 p99 才显示真实体验
一份报告说的不只是典型的等待时间,还有「最慢的 1% 的人等了这么久」——把糟糕的体验点名说出来,而不是把它们平均掉。
**Percentile(百分位数)**抓住了平均值藏起来的东西。p50(中位数)是典型情况——一半的请求比它快。p99 是慢的尾部——99% 的请求比它快,所以它大致就是真实用户会碰上的最糟体验。
盯着 p99,而不只是平均值,你才能看到那些真正在受苦的用户。痛苦就住在尾巴里,而 percentile 就是让它看得见的办法。
- 是什么
- pN 是 N% 的请求都比它快的那个时间。p50 是中位数,p99 是尾部。
- 怎么运作
- 把响应时间排序,取正确位置上的那个值。
- 例子
- p50 = 120 ms(典型),p99 = 2 s(一百个请求里有一个要等两秒)。
- 陷阱
- 不能把各服务器的 percentile 取平均——要对所有请求一起计算。
- 关联
- 让 Tail Latency 看得见;Performance Target 就设在它们上面。
Tail Latency — 用户记住的是什么
在一家餐厅吃到一顿糟糕的饭,在人的记忆里,比十几顿还不错的饭分量更重——塑造别人如何评判你的,是最糟的体验,而不是平均值。
那些慢的请求——也就是 tail latency——不成比例地塑造了用户对你产品的观感,因为糟糕的体验会让人记住。
而且在大规模下,尾巴影响的人比看上去的要多:如果每个页面都要发出许多调用,至少有一个慢的概率就会飞快上升,所以几乎每个用户最终都会感受到尾巴。这就是为什么认真的团队把目标设在 p99 上,而不是平均值:驯服最糟的那些情况,才让一个产品感觉上稳定地快。
- 是什么
- 最慢的那些请求的耗时——p99 及更往后。
- 怎么运作
- 当一个响应由许多调用拼装而成,它要等其中最慢的那一个。
- 例子
每次调用只有 1% 的时候慢,可一个有 100 次调用的页面,约有 63% 的时候会撞上尾巴。
- 陷阱
- 每个响应里的调用越多,「罕见」的尾巴就越会变成常态。
- 关联
- 通过 Percentiles 看到;被 Average 藏住。
平均值会藏住用户真正感受到的慢。去看 percentile——p50 看典型,p99 看痛苦的尾巴——因为人们记住的是最糟的体验。
还有一种与无视性能相反的失败方式:太早、到处追着性能跑,还没弄清它是否要紧。关于它的那句名言,之所以出名,是有道理的。
Premature Optimization — 万恶之源
为一场也许永远不会来的地震,把一座房子的每面墙都加固一遍,而这时还没有人搬进来——大把力气花在一个你还没有的问题上。
Donald Knuth 的那句名言——「过早优化是万恶之源」——警告的是:在你还不知道什么需要优化、或者到底有没有东西需要优化之前,就动手优化。
优化并不慢、甚至没怎么被运行的代码,什么也买不到,却花费不菲:复杂度、bug,以及从要紧事上偷走的时间。大多数代码不需要快;它需要正确而清晰。提速的工作,是留给测量证明为**热点(hot)**的那些部分的。
- 是什么
- 在测量证明确有必要之前就去优化。
- 怎么运作
- 你用清晰度和时间,在一个没人需要速度的地方换来了速度。
- 例子
- 花一周时间给一个每月才打开一次的设置页做个精巧的缓存。
- 陷阱
- 当真正的 bottleneck 出现在别处时,纠缠成一团的「快」代码只会碍事。
- 关联
用 Profiling 治好;由 Amdahl's Law 解释;别与 Performance by Design 混淆。
Correct → Clear → Fast — 先正确、清晰,在要紧处再求快
你把菜谱写得人人都能照着做,只把你一天做上一百次的那一步简化——不是每一步,只是热的那一步。
顺序是:先让它正确,再让它清晰,并且只在测量显示要紧的地方才让它变快。
清晰的代码,恰恰因为你看得懂,之后才更容易优化;当真正的 bottleneck 出现在别处时,纠缠成一团的「快」代码很难修。为速度而优化几乎总要牺牲可读性,所以你只在能换来真实、可测的收益的地方付出这个代价——其余的保持简单。
- 是什么
- 一个优先级的顺序:正确性、清晰度,最后才是速度。
- 怎么运作
- 等清晰的代码已经有了、并且测量过,再在热点处精准地加入速度。
- 例子
- 先写一个朴素易读的 handler;profile 显示有一个热循环——只优化那一个。
- 陷阱
- 优化几乎总要牺牲可读性——只把它花在真实的收益上。
- 关联
- Premature Optimization 的解药。
Performance by Design — 但在设计里也别无视性能
你不会给一间棚屋做抗震,但你也不会在沙地上盖摩天大楼——有些选择,早期做对很便宜,晚期再修则代价惨重。
这个警告并不是「别去想性能」。在 O(n) 同样容易的时候选了 O(n²) 的算法,或者选了一个撑不住规模的结构,是你日后要付大价钱的设计错误。
平衡点是:不要早早地做微观优化,但也别做出那种一开始就慢、日后又很贵才能撤销的架构选择。一开始就便宜地把大的形状做对;细节只在测量要求时才去优化。
- 是什么
- 从一开始就带着对速度的考虑,来做关于系统形状的那些大决定。
- 怎么运作
- 算法、数据结构和架构在早期选定;微调留到后面,靠测量来做。
- 例子
- 从第一天起就选用带索引的查找,而不是扫描整张表——这不算过早。
- 陷阱
- 很容易躲在这条原则后面,去做对细节的普通过早优化。
- 关联
- Premature Optimization 的边界;它的主要工具是 Better Algorithm。
过早优化是拿清晰度去换没人需要的速度。先让它正确而清晰,只在测量证明要紧的地方才求快——但也别选一个你日后会后悔的、天生就慢的形状。
做得好的性能工作,是一套平静、可重复的方法,而不是英雄式的手忙脚乱。整个实践可以装进一个短循环,而且只在确有理由时才去跑它。
Optimization Loop — 测量,修 bottleneck,重复
修车师傅用仪器诊断,修那一个坏掉的零件,再复测——从不乱换零件,指望噪音自己消失。
方法简单而有纪律:测量以找到 bottleneck,修那一个,再测一遍来确认并暴露出下一个 bottleneck,够快了就停。
每一轮都瞄准当前占大头的开销,无视其余的一切。这个循环让性能工作保持诚实而高效——你总知道自己在做的是真正要紧的事,也总知道自己有没有帮上忙。
- 是什么
- 一个反复进行的周期:测量、找出、修一处、检查。
- 怎么运作
每一轮都是针对当前 Limiting Step 的一次 Measure–Fix–Measure。
- 例子
第 1 轮:给查询加个索引。第 2 轮:现在渲染是 bottleneck——缓存模板。达到目标——停。
- 陷阱
- 没有目标,循环永远不会结束。
- 关联
- 把 Profiling、Bottleneck 和 Performance Target 串在一起。
Performance Target — 定个目标,然后收手
你给房子做保温,做到够暖和就停——不会一直往里加保温层,加到没人感觉得到的地步。
性能没有天然的终点——你总能把什么东西再弄快一点点。所以要定一个 performance target(「页面在 p99 下低于 300 ms」),达到了就停。
没有目标,优化就会变成一个收益递减、没完没了的时间黑洞,复杂度也会为没人注意到的收益悄悄混进来。知道「够快」对你的用户、你的场景意味着什么,才让你做刚刚好的性能工作,然后去建点别的东西。
- 是什么
- 一个具体的「够快」数字,绑定在某个 percentile 上。
- 怎么运作
- 只要指标还高于目标,优化循环就继续跑;一旦低于目标就停。
- 例子
- 「首页 p99 低于 300 ms。」
- 陷阱
- 把目标设在平均值上会骗到你——要设在 p99 上。
- 关联
- Optimization Loop 的出口;用 Percentiles 度量。
整体是怎么拼在一起的
整条路线在一张地图上:你从哪儿出发,经过哪些站,在哪儿下车。
这些概念拼成一幅图。Profiling 找到 bottleneck;目标——latency 还是 throughput——告诉你该修什么。Bottleneck 通常由四个经典办法之一治好:更好的算法、更少的往返、缓存,或者少做事。然后你再测一遍,按 percentile,对照一个目标,然后收手。而过早优化,就是你跳过测量时发生的事。
- 我测量过吗——时间到底花在了哪儿,还是我在猜?
- Latency 还是 throughput——我实际需要的是哪种快?
- Bottleneck 是什么——那个占大头时间的部分是哪个?
- 它是个经典问题吗——算法不好、往返太多、缺缓存、多余的工作?
- 我在盯 p99 吗,而不只是会藏住慢尾巴的平均值?
- 有没有一个目标可以朝它优化,好让我知道何时收手?
- 你动手前先测量,动手后再测量。
- 你修的是 bottleneck,不去管那些本来就够快的部分。
- 你的收益来自算法、更少的往返和 caching——而不是微调。
- 你用 p99 来判断速度,而不是一个好听的平均值。
- 你朝着一个目标优化并收手,让其余部分保持清晰而正确。
速查表:全部 20 个概念
测量
- Profilingprofiler
测量程序到底把时间花在哪儿,细到每个函数。
- Measure–Fix–Measure再测一遍
每次修复都是一个假设,要靠第二次测量来确认。
速度目标
- Latency响应时间
一次操作从开始到结束的时间——用户感受到的东西。
- Throughput每秒请求数
系统每秒处理多少工作。
- Batching把工作攒成批
把工作攒在一起:throughput 上去了,latency 也跟着上去。
Bottleneck
- Bottleneck占大头的开销
占掉大部分时间的那一个部分。
- Limiting Step下一个 bottleneck
系统只能和它最慢的一步一样快;修好它,下一个就会冒出来。
- Amdahl's Law被占比封顶
你的收益,被加速部分所占的时间份额封顶。
最大的几类提速
- Better AlgorithmBig-O
换思路:O(n²) → O(n),胜过任何硬件。
- N+1 Query多余的往返
对列表的每一项各发一条查询;靠批量获取治好。
- Caching记住结果
记住昂贵的结果,不再重新计算。
- Doing Less惰性求值
跳过多余的工作:惰性计算,只取你需要的数据,不产生被扔掉的结果。
尾巴
- Average (Mean)平均值陷阱
一个平静的数字,藏住了慢的情况。
- Percentilesp50 / p99
p50 是典型情况,p99 大致是真实用户会碰上的最糟情况。
- Tail Latency慢的尾巴
最慢的那些请求;人们会记住它们,而调用一多,几乎每个人都会撞上。
克制
- Premature Optimization优化一个猜测
在测量显示有需要之前就去提速。
- Correct → Clear → Fast先能跑、再对、再快
先正确,再清晰,只在热点处求快。
- Performance by Design大的形状
一开始就把大的形状(算法、结构、架构)做对。
实践
- Optimization Loop循环
测量 → 修一个 bottleneck → 再测量 → 重复。
- Performance Target够快
一个 p99 上的「够快」数字——循环在这里停下。
性能是一个平静的循环:测量,修那一个 bottleneck,再测量,达到目标就停。只在证明确有必要的地方用它,其余一切都保持清晰。