diff --git a/.gitignore b/.gitignore index 4b4a6c5..09a1540 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ lcov.info *.hermes.js *.hermes.hbc +*.bundle.js diff --git a/README.md b/README.md index c0a6d92..9867144 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,8 @@ Here is a brief explanation, and you can see [archived benchmark results](benchm **Performance in React Native**: `unicode-segmenter/grapheme` is significantly faster than alternatives when compiled to Hermes bytecode. It's 3\~8x faster than `graphemer` and 20\~26x faster than `grapheme-splitter`, with the performance gap increasing with input size. +**Performance in QuickJS**: `unicode-segmenter/grapheme` is the only usable library in terms of performance. + Instead of trusting these claims, you can try `yarn perf:grapheme` directly in your environment or build your own benchmark. ## LICENSE diff --git a/benchmark/grapheme/perf-quickjs.js b/benchmark/grapheme/perf-quickjs.js new file mode 100644 index 0000000..1a51c95 --- /dev/null +++ b/benchmark/grapheme/perf-quickjs.js @@ -0,0 +1,45 @@ +import * as path from 'node:path'; +import { build } from 'esbuild'; +import { $ } from 'zx'; + +let baseDir = import.meta.dirname; + +let libs = [ + ['unicode-segmenter/grapheme', 'bundle-entries/unicode-segmenter.js', 'perf-quickjs/unicode-segmenter.js'], + ['graphemer', 'bundle-entries/graphemer.js', 'perf-quickjs/graphemer.js'], + ['grapheme-splitter', 'bundle-entries/grapheme-splitter.js', 'perf-quickjs/grapheme-splitter.js'], +]; + +let benches = []; + +for (let lib of libs) { + let libName = lib[0]; + let libEntry = path.join(baseDir, lib[1]); + let execEntry = path.join(baseDir, lib[2]); + let bundleEntry = libEntry.replace(/\.js$/, '.bundle.js'); + + await build({ + write: true, + bundle: true, + minify: true, + format: 'esm', + entryPoints: [libEntry], + outfile: bundleEntry, + }); + + benches.push({ + libName, + libEntry, + execEntry, + bundleEntry, + }); +} + +console.log('\nExecuting QuickJS benchmark...\n'); + +let args = process.argv.slice(2).join(' '); + +for (let bench of benches) { + console.log(`--- ${bench.libName} ---\n`); + await $({ stdio: 'inherit' })`qjs ${args} ${bench.execEntry}`; +} diff --git a/benchmark/grapheme/perf-quickjs/grapheme-splitter.js b/benchmark/grapheme/perf-quickjs/grapheme-splitter.js new file mode 100644 index 0000000..bba0189 --- /dev/null +++ b/benchmark/grapheme/perf-quickjs/grapheme-splitter.js @@ -0,0 +1,28 @@ +import { inputs, simpleBench } from '../../_simple-bench.js'; + +import { GraphemeSplitter } from '../bundle-entries/grapheme-splitter.bundle.js'; + +let graphemeSplitter = new (GraphemeSplitter.default || GraphemeSplitter)(); + +{ + let result = simpleBench(1000, () => { + void [...graphemeSplitter.iterateGraphemes(inputs.small)]; + }); + + print(`grapheme-splitter (small input)`); + print(`samples: ${result.samples}`); + print(`duration (avg): ${result.avgDuration}`); + print(); +} + + +{ + let result = simpleBench(1000, () => { + void [...graphemeSplitter.iterateGraphemes(inputs.medium)]; + }); + + print(`grapheme-splitter (medium input)`); + print(`samples: ${result.samples}`); + print(`duration (avg): ${result.avgDuration}`); + print(); +} diff --git a/benchmark/grapheme/perf-quickjs/graphemer.js b/benchmark/grapheme/perf-quickjs/graphemer.js new file mode 100644 index 0000000..3f6099f --- /dev/null +++ b/benchmark/grapheme/perf-quickjs/graphemer.js @@ -0,0 +1,28 @@ +import { inputs, simpleBench } from '../../_simple-bench.js'; + +import { Graphemer } from '../bundle-entries/graphemer.bundle.js'; + +let graphemer = new (Graphemer.default || Graphemer)(); + +{ + let result = simpleBench(1000, () => { + void [...graphemer.iterateGraphemes(inputs.small)]; + }); + + print(`graphemer (small input)`); + print(`samples: ${result.samples}`); + print(`duration (avg): ${result.avgDuration}`); + print(); +} + + +{ + let result = simpleBench(1000, () => { + void [...graphemer.iterateGraphemes(inputs.medium)]; + }); + + print(`graphemer (medium input)`); + print(`samples: ${result.samples}`); + print(`duration (avg): ${result.avgDuration}`); + print(); +} diff --git a/benchmark/grapheme/perf-quickjs/unicode-segmenter.js b/benchmark/grapheme/perf-quickjs/unicode-segmenter.js new file mode 100644 index 0000000..79e5a18 --- /dev/null +++ b/benchmark/grapheme/perf-quickjs/unicode-segmenter.js @@ -0,0 +1,25 @@ +import { inputs, simpleBench } from '../../_simple-bench.js'; + +import { graphemeSegments } from '../bundle-entries/unicode-segmenter.js'; + +{ + let result = simpleBench(1000, () => { + void [...graphemeSegments(inputs.small)]; + }); + + print(`unicode-segmenter/grapheme (small input)`); + print(`samples: ${result.samples}`); + print(`duration (avg): ${result.avgDuration}`); + print(); +} + +{ + let result = simpleBench(1000, () => { + void [...graphemeSegments(inputs.medium)]; + }); + + print(`unicode-segmenter/grapheme (medium input)`); + print(`samples: ${result.samples}`); + print(`medium input (avg): ${result.avgDuration}`); + print(); +} diff --git a/package.json b/package.json index f7fa31a..b47d856 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "perf:general": "node benchmark/general/perf.js", "perf:grapheme": "node benchmark/grapheme/perf.js", "perf:grapheme:browser": "vite benchmark/grapheme", - "perf:grapheme:hermes": "node benchmark/grapheme/perf-hermes.js" + "perf:grapheme:hermes": "node benchmark/grapheme/perf-hermes.js", + "perf:grapheme:quickjs": "node benchmark/grapheme/perf-quickjs.js" }, "alias": { "process": false