资讯编译链路硬核优化:源码到执行闭环打通
|
去年七月份那波优化,现在想起来还手心冒汗。监控大屏的绿光刺得眼睛疼,我盯着某库某表的编译耗时曲线,从十八秒直接跌到三秒——不是优化后的三秒,是优化前十八秒,优化后反而三秒?当时差点把咖啡杯砸了,第三杯已经凉透,杯底还沉着半块方糖。 问题出在闭环上。原以为把编译流程拆成源码解析、中间代码生成、执行计划生成三个阶段,再分别用不同线程池跑,就能提速。结果开发说:“你这线程池配比不对,解析阶段CPU密集型,生成阶段IO密集型,执行阶段又变成内存密集型,混在一起跑,反而互相阻塞。”我原以为他瞎扯,结果拿perf顶上去一看,好家伙,解析线程在等生成线程的锁,生成线程在等执行线程的磁盘IO——三个阶段互相卡脖子,比早高峰的地铁换乘还热闹。 最坑的是执行计划生成。我们用了某开源框架的优化器,默认开启“激进优化”模式,结果对某些复杂SQL直接死循环。开发说:“这框架的优化器有bug,某些情况下会无限递归。”我让他给复现案例,他甩过来一个三百行的SQL,说:“你跑跑看。”我跑了两小时,监控大屏的绿光都快把我照成绿巨人,终于卡在某个子查询的JOIN条件上——优化器在这儿疯狂试不同的执行路径,就是出不来结果。
文章配图,仅供参考 后来怎么解决的?直接关了“激进优化”,改用“保守优化”。开发说:“保守优化虽然慢,但至少不会死循环。”我怼他:“那之前谁提议用激进优化的?”他回:“不是你拍板的吗?”我……行吧,这锅我背。但关了激进优化后,编译耗时从十八秒降到十二秒,虽然还是没达到预期,但至少不再卡死了。真正打通闭环是在八月份。那天深夜,我盯着执行计划的日志,突然发现一个细节:生成阶段的中间代码,有大量重复的表达式计算。比如同一个表的不同字段,在多个WHERE条件里被重复计算。我原以为这是框架的优化问题,结果开发说:“这是你们业务代码写的烂,同一个条件写了三遍。”我让他给具体案例,他甩过来一个工单,工单里附了段SQL,我一看,好家伙,同一个`user_id = ?`条件,在WHERE、HAVING、ORDER BY里各写了一遍——这开发怕不是复制粘贴时手滑了? 但这也给了我灵感:如果能在编译阶段识别重复表达式,提前合并计算,是不是能省点时间?于是我们改了中间代码生成逻辑,加了个表达式去重模块。开发说:“这模块会拖慢生成阶段。”我说:“拖慢就拖慢,只要执行阶段能快就行。”结果测试下来,生成阶段从四秒变成五秒,但执行阶段从八秒降到四秒——总耗时从十二秒降到九秒,比之前又快了三秒。 这时候我才明白,所谓“源码到执行闭环打通”,不是把每个阶段都优化到极致,而是让每个阶段的输出,都能成为下一个阶段的输入,而不是互相拖后腿。就像早高峰的地铁,换乘通道再宽,如果进站口堵了,出站口挤了,照样没用。优胜劣汰的逻辑在这儿特别明显:哪个阶段拖后腿,就优先优化哪个阶段;哪个阶段的输出对下个阶段没用,就直接砍掉——比如那个死循环的激进优化,直接砍了,反而更快。 现在回头看,那波优化最值的不是耗时降了多少,而是把整个编译链路的瓶颈都摸透了。以前开发说“这里慢”,我只能猜是网络问题、磁盘问题还是CPU问题;现在我能直接看perf数据,看哪个函数占了多少CPU,哪个锁争用了多少次,甚至能看出是业务代码写的烂,还是框架有bug——这种底气,是闭环打通后才有的。 不过也有遗憾。比如那个表达式去重模块,后来发现对某些复杂SQL反而会拖慢生成阶段——比如嵌套子查询里,重复表达式可能分布在不同层级,强行去重会导致中间代码结构混乱,反而影响执行计划生成。这时候就只能回滚,或者加更多判断逻辑。开发说:“这就是优化,没有银弹。”我点头:“对,优胜劣汰,能用的留下,不能用的砍掉。” 现在监控大屏的绿光还是刺眼,但至少编译耗时稳定在八秒以内了。偶尔看到某库某表的曲线突然跳到十秒,我也不慌——知道是哪里卡了,知道怎么查,知道怎么修,这就够了。优胜劣汰的逻辑,在这儿算是玩明白了。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


