做加法还是做减法?搞懂 Tree-shaking 与死代码消除(DCE)机制
文章目录
翻到一篇老文章 Tree-shaking versus dead code elimination,关于 Tree-shaking 和 Dead Code Elimination (DCE) 的,很多人会将它们放到一起讨论,本文来简单介绍一下。
Dead Code Elimination
死代码消除(Dead Code Elimination, DCE)是一种编译器优化技术,通过移除程序中永远不会执行(不可达)或执行结果从不被使用的代码,来减小生成的可执行文件体积并提高运行效率。
在编译器中,死代码通常包含以下几种:
- 不可达代码 (Unreachable Code)。 无论程序如何运行,控制流都永远无法到达的代码(例如
return语句之后的代码,或者if (false)块内的代码)。 - 无用赋值 (Dead Store / Useless Code)。 执行了计算,但其结果在后续程序中从未被使用的代码。
- 无用函数(Useless Function)。在链接或最终打包之后,程序中任何地方都没有引用的函数或方法。
- 无用数据(Useless Data)。已定义但未引用的数据结构、常量和字段。
|
|
DCE 的核心思想是消除。编译器在生成了中间代码(IR)并构建了控制流图(CFG)之后,会进行数据流分析。DCE 依赖于静态分析(Static Analysis)技术,特别是对控制流和数据流的分析:
- 控制流分析:构建控制流图(CFG),识别从程序入口(如
main函数)开始无法到达的节点。 - 活性分析 (Liveness Analysis):确定变量在程序中的哪些点是“活的”(即其当前值在未来会被读取)。如果一个赋值操作的目标变量不再是活的,该赋值即可被消除。
- 副作用检查:编译器必须非常保守,如果无法证明移除某段代码不会改变程序的外部行为(如打印日志或修改硬件寄存器),则必须保留该代码。
DCE 主要有以下好处:
- 减小体积:移除未引用的函数、类和变量,显著减小二进制文件或 Web 束(Bundle)的大小,缩短加载时间。
- 提升性能:减少 CPU 需要执行的指令数量,节省寄存器资源,并可能通过减少指令空间占用而改善缓存局部性。
- 简化后续优化:消除死代码能简化程序的结构,使编译器其他优化阶段(如内联、常量折叠)更易发现进一步优化的机会。
- 提高可维护性:如果开发者可以在源代码层面根据结果手动清理死代码,可以降低开发者的认知负荷,防止误导后续的调试和开发工作。
Tree-shaking
Tree-shaking 是一种在 JavaScript 生态中广泛使用的消除无用代码的技术。JavaScript 是一门动态语言,早期的 CommonJS 模块规范(require())是动态加载的,这意味着在代码真正运行之前,打包工具很难静态分析出到底哪些模块被使用了。 直到 ES6 Modules(ESM)的出现,通过强制的静态结构(import 和 export 只能出现在模块顶层),才为 Tree-shaking 提供了大展拳脚的舞台。
同样地,Tree-shaking 也是为了可以减少体积和提升性能等。
Tree-shaking 的核心思想不是“消除”,而是保留(Live Code Inclusion)。 构建工具会把程序的入口文件视为“树根”。然后,它会基于抽象语法树(AST)去追踪所有的 import 语句,就像顺着树干寻找树枝和树叶。只有那些被清晰地追踪到、并且被实际使用的函数或变量(活叶子),才会被纳入最终的打包产物中。
|
|
题外
| 维度 | Dead Code Elimination (DCE) | Tree-shaking |
|---|---|---|
| 思维模型 | 排除法(Elimination) | 包含法(Inclusion) |
| 执行路径 | 自下而上:先全盘接收,再剔除无用 | 自上而下:从入口出发,只打包有用 |
| 依赖基础 | 控制流图 (CFG)、数据流分析 | 抽象语法树 (AST)、ES6 静态模块结构 |
| 典型应用域 | 传统静态语言编译器 (如 GCC/Clang 优化) | 现代前端模块打包工具 (如 Rollup, Webpack) |
简单来说,DCE 是在做减法,而 Tree-shaking 是在做加法。
无论是 DCE 还是 Tree-shaking,在面对实际工程时,都会遇到一个棘手的问题:副作用。编译器的第一准则是:永远不要改变程序的原有执行逻辑。所以它们的策略都会偏向于保守。
除了副作用带来的优化瓶颈,作用域的局限也是一个问题。传统的 DCE 通常局限于单个编译单元,如果函数 A 在文件 1 中定义,在文件 2 中没有被调用,文件 1 编译时并不知道它到底有没有被用过,所以不敢删除。而 LTO 把视野扩大到了全局链接阶段,让跨文件的 DCE 成为了可能。之前还讲过在链接期的优化 LTO,也有优化代码的效果,感兴趣可以翻翻我以前的文章。
参考
文章作者 calssion
上次更新 2026-03-01