SWC 和 Babel 的性能比较
JavaScript 是单线程的。JS 线程不适合进行繁重的计算。让我们来谈谈 babel
和 swc
,它们都是计算密集型的。
同步基准测试
让我们为单核工作负载做一个基准测试。请注意,这里使用了 transformSync
,这在实际情况中很少有用。
[transform]
swc (es3) x 616 ops/sec ±4.36% (88 runs sampled)
swc (es2015) x 677 ops/sec ±2.01% (90 runs sampled)
swc (es2016) x 1,963 ops/sec ±0.45% (93 runs sampled)
swc (es2017) x 1,971 ops/sec ±0.35% (94 runs sampled)
swc (es2018) x 2,555 ops/sec ±0.35% (93 runs sampled)
swc-optimize (es3) x 645 ops/sec ±0.40% (90 runs sampled)
babel (es5) x 34.05 ops/sec ±1.15% (58 runs sampled)
SWC 非常快。尽管 swc (es3)
比 babel (es5)
做更多的工作,但 swc (es3)
比 babel (es5)
更快。
真实世界基准测试
transformSync
和 transformFileSync
在现实世界中很少使用,因为它会阻塞当前线程。await Promise.all()
经常被使用,因为它比以下方式更好:
for (const promise of promises) {
await promise;
}
让我们使用 Promise.all()
创建一个实际使用场景的基准测试。
理想情况
首先,我为理想情况创建了一个基准测试。它一次调用 [n]
个 Promise,其中 n
是物理 CPU 核心的数量。完整代码请参见 node-swc 仓库 (opens in a new tab)。
const os = require("os");
const cpuCount = os.cpus().length;
const SOURCE = `
// 参见上面的链接
`;
const SUITES = [
// ...
// 参见上面的链接
];
const arr = [];
for (let i = 0; i < cpuCount / 2; i++) {
arr.push(0);
}
console.info(`CPU 核心数: ${cpuCount}; 并行度: ${arr.length}`);
console.info(
`请注意,此基准测试的输出应乘以 ${arr.length},因为此测试使用了 Promise.all`
);
SUITES.map(args => {
const [name, requirePath, fn] = args;
const func = fn.bind(null, require(requirePath));
bench(name, async done => {
await Promise.all(arr.map(v => func()));
done();
});
});
我在我的旧台式机上运行了基准测试。它有 E3-v1275 处理器和 24GB 内存。下面的输出是从基准测试输出中直接复制的。
CPU Core: 8; Parallelism: 4
Note that output of this benchmark should be multiplied by 4 as this test uses Promise.all
[multicore]
swc (es3) x 426 ops/sec ±3.75% (73 runs sampled)
swc (es2015) x 422 ops/sec ±3.57% (74 runs sampled)
swc (es2016) x 987 ops/sec ±2.53% (75 runs sampled)
swc (es2017) x 987 ops/sec ±3.44% (75 runs sampled)
swc (es2018) x 1,221 ops/sec ±2.46% (77 runs sampled)
swc-optimize (es3) x 429 ops/sec ±1.94% (82 runs sampled)
babel (es5) x 6.82 ops/sec ±17.18% (40 runs sampled)
现在,我们需要将其乘以 4,因为每次迭代我们执行 4 次操作。
swc (es3) x 1704 ops/sec ±3.75% (73 runs sampled)
swc (es2015) x 1688 ops/sec ±3.57% (74 runs sampled)
swc (es2016) x 3948 ops/sec ±2.53% (75 runs sampled)
swc (es2017) x 3948 ops/sec ±3.44% (75 runs sampled)
swc (es2018) x 4884 ops/sec ±2.46% (77 runs sampled)
swc-optimize (es3) x 1716 ops/sec ±1.94% (82 runs sampled)
babel (es5) x 27.28 ops/sec ±17.18% (40 runs sampled)
这是一个实际结果。
babel (es5)
的性能下降了。异步操作并不是免费的。尽管如此,34.05 次操作/秒
=> 27.28 次操作/秒
比我预期的要好得多。
多操作基准测试
我稍微修改了基准测试文件,使其每次迭代创建 100 个 Promise。
CPU Core: 8; Parallelism: 100
Note that output of this benchmark should be multiplied by 100 as this test uses Promise.all
[multicore]
swc (es3) x 21.99 ops/sec ±1.83% (54 runs sampled)
swc (es2015) x 19.11 ops/sec ±3.39% (48 runs sampled)
swc (es2016) x 55.80 ops/sec ±6.97% (71 runs sampled)
swc (es2017) x 62.59 ops/sec ±2.12% (74 runs sampled)
swc (es2018) x 81.08 ops/sec ±5.22% (75 runs sampled)
swc-optimize (es3) x 18.60 ops/sec ±2.13% (50 runs sampled)
babel (es5) x 0.32 ops/sec ±19.10% (6 runs sampled)
必须如上所述乘以 100。
swc (es3) x 2199 ops/sec ±1.83% (54 runs sampled)
swc (es2015) x 1911 ops/sec ±3.39% (48 runs sampled)
swc (es2016) x 5580 ops/sec ±6.97% (71 runs sampled)
swc (es2017) x 6259 ops/sec ±2.12% (74 runs sampled)
swc (es2018) x 8108 ops/sec ±5.22% (75 runs sampled)
swc-optimize (es3) x 1860 ops/sec ±2.13% (50 runs sampled)
babel (es5) x 32 ops/sec ±19.10% (6 runs sampled)
为什么 SWC 的性能不会急剧下降?秘密在于 Node.js。Node.js 内部管理着一个工作线程池,而 SWC 运行在其中。因此,即使你一次性创建了 100 个 Promise,工作线程的数量也远少于它。
结论
名称 | 1 核心,同步 | 4 个 Promise | 100 个 Promise |
---|---|---|---|
swc (es3) | 616 次/秒 | 1704 次/秒 | 2199 次/秒 |
swc (es2015) | 677 次/秒 | 1688 次/秒 | 1911 次/秒 |
swc (es2016) | 1963 次/秒 | 3948 次/秒 | 5580 次/秒 |
swc (es2017) | 1971 次/秒 | 3948 次/秒 | 6259 次/秒 |
swc (es2018) | 2555 次/秒 | 4884 次/秒 | 8108 次/秒 |
swc-optimize (es3) | 645 次/秒 | 1716 次/秒 | 1860 次/秒 |
babel (es5) | 34.05 次/秒 | 27.28 次/秒 | 32 次/秒 |
swc
扩展性很好,因为它几乎所有的操作都在工作线程中完成。从 100 个 promises
的吞吐量优于 4 个 promises
的事实可以得出结论,Node.js 的工作线程池利用了超线程技术。
swc
随着 CPU 核心数量的增加而扩展。Promise.all
足以实现扩展。