1.使用Go语言,编译成原生代码
很多打包工具是用js写的,但是命令行工具对于JIT编译语言是在性能方面最拉垮。每次打包,js编译器都会把代码当作第一次运行,没有任何优化。当esbuild忙着解析你的js,node忙着解析你的打包工具的js。当node完成包代码解析,esbuild已经可能已经退出了,而你的打包工具甚至还没有开始。
另外,Go被设计成并行的,但js不是。Go的线程之间共享内存,然而js在线程之间序列化数据。Go和js都有并行的垃圾收集器。但是在Go的堆在线程间是共享的,然而js每个线程都有独立的堆。根据我的测试,这似乎将 JavaScript 工作线程可能的并行量减少了一半,大概是因为你的 CPU 核心的一半正忙于为另一半收集垃圾。
2.并行性被大量使用
esbuild内部算法经过精心设计,以在可能的情况下使所有可用的 CPU 内核完全饱和。有三个解析阶段:解析,链接,生成代码。解析和生成代码是主要工作,并行性强(链接在很大程度上是一项固有的串行任务)。因为所有的线程共享内存,在导入相同的js库时候,功能可以在不同的点共享。现在很多电脑都是多核的,所有并行是一项很大的胜利。
3.esbuild 中的一切都是从头开始编写的
自己写代码而不是使用第三方库,有很多性能优势。您可以从一开始就考虑性能,确保所有的东西都使用了相同的数据结构,这样可以避免昂贵的转换。并且您可以在必要时进行广泛的架构更改,缺点当然是工作量很大。
比如很多打包工具使用官方的TS编译器作为解析器。但是这是为TS编译器团队的目标服务的,他们没有将性能作为重中之重。他们的代码大量使用了巨型对象形状和不必要的动态属性访问(这两个都是众所周知的 JavaScript 减速带)。
即使类型检查被禁用,TypeScript 解析器似乎仍然运行类型检查器。这些问题, esbuild 的自定义 TS 解析器都没有。
4.内存高效利用
理想情况下,编译器的输入长度主要为 O(n) 复杂度。因此,如果您正在处理大量数据,内存访问速度可能会严重影响性能。数据传输的越少(并且将数据转换成的不同表示形式也越少),编译器运行的越快(好像是废话)。
比如,esbuild只会接触整个js的AST数三次
1.第一次是词法,全局安装,声明变量
2.第二次是构建变量,。。这一部分不会翻译,剩下的2023年再写。。
5.内存数据紧凑
可以在内存里紧凑的存储数据,这一点可以使用更少的内存,更适合CPU缓存。所有的对象字段都有类型,而且字段被紧凑的打包在一起。例如,几个布尔标志每个只占用一个字节。Go也有值语义,可以将一个对象直接嵌入到另一个对象中,这样它就“免费”而无需另一个分配。js没有这些特性,而且还有其他特点,比如JIT开销(比如,隐藏类槽),低效的声明(比如非整数用指针分配在堆上)。
这些因素中的每一个都只是略微显着的加速,但是放在一起就比今天常用的打包器快几个数量级。
网友评论