Skip to content

Commit

Permalink
docs: update performance benchmarks to include exposing an AST
Browse files Browse the repository at this point in the history
  • Loading branch information
acutmore committed Sep 13, 2024
1 parent c60af02 commit 4d50d79
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 34 deletions.
91 changes: 75 additions & 16 deletions perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You will also need to install https://github.com/sharkdp/hyperfine

- Node.js v20.11.1
- Apple M2 Pro 32GB
- 1 File read done in Node.js
- 1 file read done using `node:fs`
- 3 warmup runs
- 10 measured runs

Expand All @@ -37,55 +37,114 @@ You will also need to install https://github.com/sharkdp/hyperfine
| @swc/wasm | sync wasm | 2.679 s ± 0.011 s |
| esbuild-wasm | async wasm | 5.448 s ± 0.018 s |
| esbuild-wasm | sync wasm | 6.264 s ± 0.028 s |
| typescript | sync JS | 6.375 s ± 0.086 s |
| typescript | sync JS | 7.068 s ± 0.061 s |
| @babel/core | sync JS | 7.517 s ± 0.060 s |

```sh
Benchmark: node ./esbuild-async.js ./fixtures/checker.txt 10
Benchmark: esbuild
Time (mean ± σ): 309.6 ms ± 11.5 ms [User: 113.1 ms, System: 48.4 ms]
Range (min … max): 296.2 ms … 332.1 ms 10 runs

Benchmark: node ./swc-native-async.js ./fixtures/checker.txt 10
Benchmark: @swc/core
Time (mean ± σ): 512.2 ms ± 10.4 ms [User: 1667.0 ms, System: 101.8 ms]
Range (min … max): 497.0 ms … 526.1 ms 10 runs

Benchmark: node ./esbuild-sync.js ./fixtures/checker.txt 10
Benchmark: esbuild:sync
Time (mean ± σ): 1.146 s ± 0.011 s [User: 1.640 s, System: 0.156 s]
Range (min … max): 1.127 s … 1.161 s 10 runs

Benchmark: node ./ts-blank-space.js ./fixtures/checker.txt 10
Benchmark: ts-blank-space
Time (mean ± σ): 1.255 s ± 0.014 s [User: 2.090 s, System: 0.130 s]
Range (min … max): 1.235 s … 1.281 s 10 runs

Benchmark: node ./swc-wasm-strip.js ./fixtures/checker.txt 10
Benchmark: @swc/wasm-typescript
Time (mean ± σ): 1.436 s ± 0.012 s [User: 1.646 s, System: 0.046 s]
Range (min … max): 1.425 s … 1.457 s 10 runs

Benchmark: node ./sucrase.js ./fixtures/checker.txt 10
Benchmark: sucrase
Time (mean ± σ): 1.537 s ± 0.037 s [User: 2.206 s, System: 0.231 s]
Range (min … max): 1.494 s … 1.625 s 10 runs

Benchmark: node ./swc-native-sync.js ./fixtures/checker.txt 10
Benchmark: @swc/core:sync
Time (mean ± σ): 1.580 s ± 0.019 s [User: 1.640 s, System: 0.082 s]
Range (min … max): 1.563 s … 1.634 s 10 runs

Benchmark: node ./swc-wasm.js ./fixtures/checker.txt 10
Benchmark: @swc/wasm
Time (mean ± σ): 2.679 s ± 0.011 s [User: 3.431 s, System: 0.091 s]
Range (min … max): 2.660 s … 2.701 s 10 runs

Benchmark: node ./esbuild-wasm-async.js ./fixtures/checker.txt 10
Benchmark: esbuild-wasm
Time (mean ± σ): 5.448 s ± 0.018 s [User: 0.122 s, System: 0.045 s]
Range (min … max): 5.421 s … 5.481 s 10 runs

Benchmark: node ./esbuild-wasm-sync.js ./fixtures/checker.txt 10
Benchmark: esbuild-wasm:sync
Time (mean ± σ): 6.264 s ± 0.028 s [User: 8.124 s, System: 0.269 s]
Range (min … max): 6.233 s … 6.306 s 10 runs

Benchmark: node ./typescript.js ./fixtures/checker.txt 10
Time (mean ± σ): 6.375 s ± 0.086 s [User: 9.701 s, System: 0.405 s]
Range (min … max): 6.249 s … 6.549 s 10 runs
Benchmark: typescript
Time (mean ± σ): 7.068 s ± 0.061 s [User: 10.395 s, System: 0.397 s]
Range (min … max): 6.955 s … 7.150 s 10 runs

Benchmark 1: node ./babel.js ./fixtures/checker.txt 10
Benchmark: @babel/core
Time (mean ± σ): 7.517 s ± 0.060 s [User: 11.499 s, System: 0.653 s]
Range (min … max): 7.419 s … 7.615 s 10 runs
```

### Including accessing TypeScript AST

One of the benefits of `ts-blank-space` is that it can [reuse an existing TypeScript AST](../README.md#bring-your-own-ast).

This is important for Bloomberg's use case because we also require JavaScript access to the TypeScript AST for analysis.
Here are the results of the experiment when we also include the cost of exposing the AST to JavaScript.

`@babel/core` (via `@babel/parser`), `@swc/core`, and `@swc/wasm` expose an AST. `esbuild`, `sucrase`, and `@swc/wasm-typescript` do not expose an AST, for these packages `typescript` is used to provide an AST.

| package | runtime | mean time ± σ |
| -------------------- | ---------- | ----------------- |
| ts-blank-space | sync JS | 1.255 s ± 0.014 s |
| esbuild | async Go | 1.422 s ± 0.018 s |
| @swc/core | async Rust | 1.570 s ± 0.015 s |
| sucrase | sync JS | 2.580 s ± 0.042 s |
| @swc/wasm-typescript | sync wasm | 2.597 s ± 0.029 s |
| @swc/wasm | sync wasm | 5.032 s ± 0.015 s |
| esbuild-wasm | async wasm | 6.466 s ± 0.020 s |
| typescript | sync JS | 7.068 s ± 0.061 s |
| @babel/core | sync JS | 7.609 s ± 0.130 s |

```sh
Benchmark: ts-blank-space
Time (mean ± σ): 1.255 s ± 0.014 s [User: 2.090 s, System: 0.130 s]
Range (min … max): 1.235 s … 1.281 s 10 runs

Benchmark: esbuild:ts-ast
Time (mean ± σ): 1.422 s ± 0.018 s [User: 3.256 s, System: 0.248 s]
Range (min … max): 1.397 s … 1.468 s 10 runs

Benchmark: @swc/core:ts-ast
Time (mean ± σ): 1.570 s ± 0.015 s [User: 4.282 s, System: 0.279 s]
Range (min … max): 1.547 s … 1.595 s 10 runs

Benchmark: sucrase:ts-ast
Time (mean ± σ): 2.580 s ± 0.042 s [User: 4.116 s, System: 0.348 s]
Range (min … max): 2.512 s … 2.662 s 10 runs

Benchmark: @swc/wasm-typescript:ts-ast
Time (mean ± σ): 2.597 s ± 0.029 s [User: 3.669 s, System: 0.168 s]
Range (min … max): 2.565 s … 2.646 s 10 runs

Benchmark: @swc/wasm:ts-ast
Time (mean ± σ): 5.032 s ± 0.015 s [User: 6.611 s, System: 0.273 s]
Range (min … max): 5.009 s … 5.064 s 10 runs

Benchmark: esbuild-wasm:ts-ast
Time (mean ± σ): 6.466 s ± 0.020 s [User: 2.143 s, System: 0.166 s]
Range (min … max): 6.438 s … 6.508 s 10 runs

Benchmark: typescript
Time (mean ± σ): 7.068 s ± 0.061 s [User: 10.395 s, System: 0.397 s]
Range (min … max): 6.955 s … 7.150 s 10 runs

Benchmark: @babel/core:ts-ast
Time (mean ± σ): 7.609 s ± 0.130 s [User: 11.573 s, System: 0.747 s]
Range (min … max): 7.428 s … 7.818 s 10 runs
```
18 changes: 17 additions & 1 deletion perf/babel.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
import * as babel from "@babel/core";
import * as babelParser from "@babel/parser";
import * as fs from "node:fs";

function assert(v) {
Expand All @@ -8,6 +9,7 @@ function assert(v) {

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

const options = {
plugins: [
Expand All @@ -19,10 +21,24 @@ const options = {
configFile: false,
sourceMaps: true,
browserslistConfigFile: false,
cloneInputAst: false,
};

/** @type {babelParser.ParserOptions} */
const parseOptions = {
sourceType: "module",
sourceFilename: "input.ts",
plugins: [["typescript", {}]],
};

for (let i = 0; i < count; i++) {
const output = babel.transformSync(input, options);
let output;
if (parseTS) {
const ast = babelParser.parse(input, parseOptions);
output = babel.transformFromAstSync(ast, input, options);
} else {
output = babel.transformSync(input, options);
}
assert(output.map.mappings.length > 100);
assert(output.code.length > 100);
}
33 changes: 20 additions & 13 deletions perf/bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@ cd "$(dirname "$0")"
echo "\n"

fixture='./fixtures/checker.txt'
# Higher count to increases ratio of startup-time vs transform-time
# Higher count increases ratio of startup-time vs transform-time
count=10

hyperfine --warmup 3 \
"node ./ts-blank-space.js $fixture $count"\
"node ./sucrase.js $fixture $count"\
"node ./swc-native-async.js $fixture $count"\
"node ./swc-native-sync.js $fixture $count"\
"node ./swc-wasm.js $fixture $count"\
"node ./swc-wasm-strip.js $fixture $count"\
"node ./esbuild-async.js $fixture $count"\
"node ./esbuild-sync.js $fixture $count"\
"node ./esbuild-wasm-async.js $fixture $count"\
"node ./esbuild-wasm-sync.js $fixture $count"\
"node ./babel.js $fixture $count"\
"node ./typescript.js $fixture $count"\
-n "ts-blank-space" "node ./ts-blank-space.js $fixture $count"\
-n "sucrase" "node ./sucrase.js $fixture $count"\
-n "sucrase:ts-ast" "node ./sucrase.js $fixture $count --ts-ast"\
-n "@swc/core" "node ./swc-native-async.js $fixture $count"\
-n "@swc/core:ts-ast" "node ./swc-native-async.js $fixture $count --ts-ast"\
-n "@swc/core:sync" "node ./swc-native-sync.js $fixture $count"\
-n "@swc/wasm-typescript" "node ./swc-wasm-strip.js $fixture $count"\
-n "@swc/wasm-typescript:ts-ast" "node ./swc-wasm-strip.js $fixture $count --ts-ast"\
-n "@swc/wasm" "node ./swc-wasm.js $fixture $count"\
-n "@swc/wasm:ts-ast" "node ./swc-wasm.js $fixture $count --ts-ast"\
-n "esbuild" "node ./esbuild-async.js $fixture $count"\
-n "esbuild:ts-ast" "node ./esbuild-async.js $fixture $count --ts-ast"\
-n "esbuild:sync" "node ./esbuild-sync.js $fixture $count"\
-n "esbuild-wasm" "node ./esbuild-wasm-async.js $fixture $count"\
-n "esbuild-wasm:ts-ast" "node ./esbuild-wasm-async.js $fixture $count --ts-ast"\
-n "esbuild-wasm:sync" "node ./esbuild-wasm-sync.js $fixture $count"\
-n "@babel/core" "node ./babel.js $fixture $count"\
-n "@babel/core:ts-ast" "node ./babel.js $fixture $count --ts-ast"\
-n "typescript" "node ./typescript.js $fixture $count"\
5 changes: 5 additions & 0 deletions perf/esbuild-async.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @ts-check
import * as esbuild from "esbuild";
import * as fs from "node:fs";
import { parseTypeScriptAST } from "./ts-parse.js";

function assert(v) {
if (!v) throw new Error();
}

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

/** @type {esbuild.TransformOptions} */
const options = {
Expand All @@ -28,6 +30,9 @@ async function main() {
}),
);
}
if (parseTS) {
await parseTypeScriptAST(input, count);
}
await Promise.all(p);
}
main();
5 changes: 5 additions & 0 deletions perf/esbuild-wasm-async.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @ts-check
import * as esbuild from "esbuild-wasm";
import * as fs from "node:fs";
import { parseTypeScriptAST } from "./ts-parse.js";

function assert(v) {
if (!v) throw new Error();
}

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

/** @type {esbuild.TransformOptions} */
const options = {
Expand All @@ -28,6 +30,9 @@ async function main() {
}),
);
}
if (parseTS) {
await parseTypeScriptAST(input, count);
}
await Promise.all(p);
}
main();
6 changes: 6 additions & 0 deletions perf/sucrase.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @ts-check
import sucrase from "sucrase";
import * as fs from "node:fs";
import { parseTypeScriptAST } from "./ts-parse.js";

function assert(v) {
if (!v) throw new Error();
}

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

/** @type {sucrase.Options} */
const options = {
Expand All @@ -27,3 +29,7 @@ for (let i = 0; i < count; i++) {
assert((output.sourceMap?.mappings?.length ?? 0) > 100);
assert(output.code.length > 100);
}

if (parseTS) {
await parseTypeScriptAST(input, count);
}
21 changes: 19 additions & 2 deletions perf/swc-native-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,38 @@ function assert(v) {

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

/** @type {swc.Options} */
const options = {
filename: "input.ts",
sourceMaps: true,
isModule: true,
jsc: {
target: "es2022",
target: "esnext",
},
};

/** @type {swc.ParseOptions} */
const parseOptions = {
syntax: "typescript",
target: "esnext",
};

async function main() {
const p = [];
for (let i = 0; i < count; i++) {
const out = swc.transform(input, options);
let out;
if (parseTS) {
out = Promise.all([
swc.transform(input, options),
swc.parse(input, parseOptions).then((_ast) => {
assert(_ast.body);
}),
]).then(([out]) => out);
} else {
out = swc.transform(input, options);
}
p.push(
out.then((out) => {
assert((out.map?.length ?? 0) > 100);
Expand Down
2 changes: 1 addition & 1 deletion perf/swc-native-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const options = {
sourceMaps: true,
isModule: true,
jsc: {
target: "es2022",
target: "esnext",
},
};

Expand Down
6 changes: 6 additions & 0 deletions perf/swc-wasm-strip.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @ts-check
import * as swc from "@swc/wasm-typescript";
import * as fs from "node:fs";
import { parseTypeScriptAST } from "./ts-parse.js";

function assert(v) {
if (!v) throw new Error();
}

const input = fs.readFileSync(process.argv[2], "utf-8");
const count = Number(process.argv[3]) || 100;
const parseTS = process.argv.includes("--ts-ast");

/** @type {swc.Options} */
const options = {
Expand All @@ -20,3 +22,7 @@ for (let i = 0; i < count; i++) {
assert(!out.map);
assert(out.code.length > 100);
}

if (parseTS) {
await parseTypeScriptAST(input, count);
}
Loading

0 comments on commit 4d50d79

Please sign in to comment.