「跑得快」是四阶段的最后一步。不是因为它不重要,是因为你不先把前面三个跑完,你连真正的瓶颈数据都拿不到。
一、提前优化的诱惑:为什么人手痒
提前优化是人性问题,不是技术问题。
你刚写完一段代码,脑子里已经在想「这个循环能不能少遍历一次」「这个数据结构能不能换成更快的」「这里多查了一次数据库能不能合并」。你知道某个地方可以更快——你看到了那个可能性——手就痒。
忍住。
你不知道瓶颈在哪之前,所有的优化都可能是错的。你花了两天把一个循环从 O(n²) 优化到 O(n log n),结果 profiling 告诉你这个循环在生产环境里只跑了几毫秒,真正在吃 CPU 的是另一个你从来没注意过的组件。你优化的那个地方不是在给用户提速,是在给自己找事做。
「跑得快」的正确姿势是:先 profiling,再动手。 用火焰图,用慢查询日志,用 metrics——找到真正的热点。不要猜,要量。猜的优化是在正确的地方浪费时间,量的优化是在正确的地方花时间。
二、二八法则:性能优化里的擒贼擒王
二八法则在性能问题上普遍适用:大部分的性能消耗会被消耗在个别的流程步骤或组件上。 不是你系统的所有地方都慢,是一两个环节在拖垮全部。关键是找到它——不是猜到它,是量到它。
擒贼擒王。识别出瓶颈,把力气对准它。不要把时间花在次要环节上——一个冷路径优化了 50%,对整体体验的影响几乎是零。一个热路径优化了 5%,用户全感受到了。
怎么识别瓶颈?直觉会告诉你「那段代码很复杂,一定很慢」,但线上数据可能告诉你「那段复杂的代码处理的数据量很小,实际不怎么跑」。直觉会告诉你「这段代码很简单,肯定快」,但线上数据可能告诉你「这段简单的代码被调了几百万次,每次多花一毫秒就是几百秒的延迟」。直觉不可靠,profiling 才可靠。
三、优化有成本:快 vs 可读
代码更快,往往意味着更难读。这是所有优化都要面对的交易。
一个 for 循环拆成三个 map/filter 链式调用,快了 5%,但三个月后没人看得懂。一个递归改成迭代,快了 10%,但原来写递归的那个人已经离职了,迭代版本没人能维护。一个 hashmap 换成 tree,内存省了 30%,但你要自己写比较函数,每次 insert 都要调一次。
不是所有优化都该做。 5%、10%、30%——这些数字在业务场景下到底值不值?一个后端的批处理链路,每天凌晨跑一次,慢 5 分钟没人计较,不值得为了它把代码写成只有你一个人能看懂的版本。一个面向用户的实时查询接口,多 200ms 就有用户感知,值得优化,但优化完之后留好注释,告诉下一个接手的人你为什么这样写。
关键不是「能不能优化」,是「优化了值不值」。
四、基础设施优先:很多问题不靠改代码
性能优化的第一反应永远是改代码。但很多时候,瓶颈不在代码,在基础设施。
加个缓存,效果可能比你改几百行代码更明显。加个索引,查询从全表扫变成一次 B-tree 查找。调个连接池大小、调个 JVM 参数、调个超时时间——这些改动一行代码都不碰,但效果可能是翻倍的。
先看基础设施,再看代码。 代码是最后一步。代码改动的成本最高——要改逻辑、要验证正确性、要回归测试、要让同事 review。基础设施改动的成本低——一个 Spark 集群的资源配置参数,改完验证一下任务能不能跑就行,不用动业务逻辑。
五、案例:重写复杂代码 vs 调几个 Spark 参数
曾经做过一个离线的编排任务流。这个任务流跑着跑着就开始出问题:偶发的大数据量导致任务超时或失败。不是每次都超时——是「偶发」,数据量碰巧大了就挂。但从系统角度看,这已经严重影响了交付时效。
最初觉得是代码的问题。那套代码逻辑确实够复杂——是很多年前写的,几轮迭代下来已经没人说得清楚里面每一步在干什么。直接重写。花了不少时间把逻辑理清楚、把代码缕直、把不必要的步骤砍掉。重写完了性能确实有提升——但提升不大,没到根本上去。
后来换了个思路:先别改代码,先 profiling。结果发现瓶颈不在代码逻辑,在 Spark 集群本身的配置——有几个关键参数(并行度、内存分配、shuffle 分区数)一直是默认值。任务超时不是代码算得慢,是资源分配不合理导致 CPU 利用率上不去。
把对应 Spark 集群的几个关键配置参数调整之后,性能直接翻倍,之前的超时问题全消失了。不是代码重写没提升——是那条路径的提升空间本身有限,而真正的瓶颈在别的地方。
你不是在优化最慢的地方,你是在优化你最熟悉的地方。
六、退出信号
什么时候「跑得快」过了?没人再抱怨慢了。
「没人抱怨慢」是一个真实可感的信号。不是你仪表盘上的指标下来了,是用户不再找你「这个怎么这么慢」。他们忘了你的系统还存在——不是真忘了,是用你系统的时候不需要再花时间等它。
但「跑得快」没有终点。业务在涨,数据在涨,这个月的瓶颈不一定是下个月的。你把这次的瓶颈处理完了,下次新的瓶颈可能又在另一个环节上。性能优化是持续性的——不是一次性修完了就完了。「跑得快」的退出信号不是「你做完了」,是「现在没人抱怨了」。下一次再抱怨了,再走一遍:profiling → 找瓶颈 → 动手。
收尾
架构不是一次性的设计,是持续四阶段的演进。每一轮循环,你手里都有比上一轮更多的信息。你做得比上一轮更准、更稳、更快——但你还是从「能跑」开始,从简单开始。