diff --git a/.changeset/smooth-dragons-notice.md b/.changeset/smooth-dragons-notice.md new file mode 100644 index 0000000..f9b05c4 --- /dev/null +++ b/.changeset/smooth-dragons-notice.md @@ -0,0 +1,21 @@ +--- +"unicode-segmenter": patch +--- + +Optimize the hot loop based on a eep analysis of the V8 optimization chain. + +As the result, the bundle size, speed, and memory usage. All three axes are improved. See PR [#144](https://github.com/cometkim/unicode-segmenter/pull/144) for detailed explanation. + +- Bundle: −4.2% min+gzip, −3.9% min+brotli on `unicode-segmenter/grapheme` (2,453 → 2,351 gzip); −3.4% / −2.8% on the full entry +- Hermes bytecode: −20.6% (20,015 → 15,892 bytes), −18.6% gzipped +- Runtime (Node.js/V8, per benchmark case) + - `splitGraphemes()` 1.5–2.2x, `countGraphemes()` 1.20–1.43x, `graphemeSegments()` 1.05–1.21x, `collectGraphemes()` 1.01–1.19x. + - Bun/JSC gains are larger, and the interpreter tiers (Hermes, QuickJS) improve 5–23% +- Memory: lookup tables 20.6 kB → 19.1 kB, retained heap 228 kB → 218 kB, module init 1.7 ms → 1.5 ms + +The state compaction strategy is the major part. It is valid across all optimization tiers of the V8 runtime (Jitless, Maglev, TurboFan) and has been consistently improved across all other engines. + +Another noticeable change is `splitGraphemes()`, it now owns its loop, just like `countGraphemes()`. +It produces a 30-60% performance improvement. The increase in size is roughly free after compression, since the fourth byte-aligned copy of the loop back-references the other three. And margins are amortized by other improvements. + +All the analysis have done by Claude Opus 5, well-done! diff --git a/.claude/skills/benchmark/SKILL.md b/.claude/skills/benchmark/SKILL.md index 9b90c49..d5c5970 100644 --- a/.claude/skills/benchmark/SKILL.md +++ b/.claude/skills/benchmark/SKILL.md @@ -42,7 +42,8 @@ Never move test inputs by copy-paste: they contain invisible codepoints (ZWJ, va ## Size-sensitive coding notes (learned the hard way) -- esbuild inlines `const`-bound numeric literals into every use site but preserves `let` bindings. Shared numeric bounds in `src/grapheme.js` (`BMP_MAX`, `T1_MIN`, `T2_MIN`) are deliberately `let` — keep them that way. +- esbuild inlines `const`-bound numeric literals into every use site but preserves `let` bindings. That makes a shared `let` bound tempting for size, but it compiles to a mutable context slot the engine cannot fold into the comparison: hoisting the hottest bound in `src/grapheme.js` into a `let` measured up to 25% slower on the count loop, and removing the three shared bounds was *also* smaller after gzip. Spell hot-path bounds out as literals. +- Hot-loop helpers live near engine inlining cliffs — check them with `--trace-turbo-inlining` (prints `bytecode size: N`) after any edit to `cat()` or `nextState()`. TurboFan refuses callees at 460 bytecodes (`cat` sat at 453 before it was split into `cat`/`catRare`; falling off cost 15-45%), Maglev refuses at 100 (`nextState` must stay ≤ 99, worth 8-26% there). Measure the mid/low tiers with `node --no-turbofan` and `node --jitless`; a change can win big on TurboFan and still lose on Maglev. - Generator syntax is the deliberate choice over a class-based iterator: a class was 13–34% faster on V8 but cost ~+1.6 KiB Hermes bytecode after Babel class lowering (Hermes can't parse class syntax natively). Don't convert to a class without re-measuring V8, JSC, Hermes, and all three size metrics. - `codePointAt()` beat manual `charCodeAt` surrogate pairing on every measured engine (17–20% faster on Hermes) and is smaller. Don't "optimize" it back. diff --git a/README.md b/README.md index 823d5f1..fba998c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Utilities for text segmentation by extended grapheme cluster rules. You can split a string into graphemes by simply consuming the `splitGraphemes()` generator. -`splitGraphemes()` is a simple wrapper around `graphemeSegments()`. +It yields substrings directly, so it allocates less than `graphemeSegments()`. ```js import { splitGraphemes } from 'unicode-segmenter/grapheme'; @@ -69,7 +69,7 @@ import { splitGraphemes } from 'unicode-segmenter/grapheme'; If you need the result as an array, use `collectGraphemes()` for convenience. -This is a fast version of `[...splitGraphemes(str)]`, 2-4x faster. +This is a fast version of `[...splitGraphemes(str)]`, 1.5-2.5x faster. ```js import { collectGraphemes } from 'unicode-segmenter/grapheme'; @@ -237,8 +237,8 @@ Since [Hermes doesn't support the `Intl.Segmenter` API](https://github.com/faceb | Name | Unicode® | ESM? | Size | Size (min) | Size (min+gzip) | Size (min+br) | Size (min+zstd) | |--------------------------------------|----------|------|--------:|-----------:|----------------:|--------------:|----------------:| -| `unicode-segmenter/grapheme` | 17.0.0 | ✔️ | 8,684 | 5,133 | 2,453 | 2,220 | 2,505 | -| `unicode-segmenter/grapheme` (full*) | 17.0.0 | ✔️ | 10,494 | 5,777 | 2,732 | 2,435 | 2,796 | +| `unicode-segmenter/grapheme` | 17.0.0 | ✔️ | 7,851 | 4,911 | 2,351 | 2,134 | 2,402 | +| `unicode-segmenter/grapheme` (full*) | 17.0.0 | ✔️ | 10,257 | 5,760 | 2,638 | 2,367 | 2,695 | | `graphemer` | 15.0.0 | ✖️ | 410,435 | 95,104 | 15,752 | 10,660 | 15,911 | | `grapheme-splitter` | 10.0.0 | ✖️ | 122,254 | 23,682 | 7,852 | 4,802 | 6,753 | | `@formatjs/intl-segmenter`* | 17.0.0 | ✖️ | 268,301 | 176,759 | 45,988 | 31,701 | 45,370 | @@ -255,8 +255,8 @@ Since [Hermes doesn't support the `Intl.Segmenter` API](https://github.com/faceb | Name | Bytecode size | Bytecode size (gzip)* | |-------------------------------------|--------------:|----------------------:| -| `unicode-segmenter/grapheme` | 20,015 | 11,075 | -| `unicode-segmenter/grapheme` (full) | 20,224 | 11,213 | +| `unicode-segmenter/grapheme` | 15,892 | 9,012 | +| `unicode-segmenter/grapheme` (full) | 16,097 | 9,176 | | `graphemer` | 134,085 | 31,770 | | `grapheme-splitter` | 63,942 | 19,165 | | `@formatjs/intl-segmenter` | 329,547 | 136,751 | @@ -270,7 +270,7 @@ Retained memory per library after segmenting the benchmark corpus, measured by ` | Name | JS heap | ArrayBuffers* | |------------------------------|--------:|--------------:| -| `unicode-segmenter/grapheme` | 228 kB | 20.6 kB | +| `unicode-segmenter/grapheme` | 217 kB | 19.1 kB | | `graphemer` | 2.32 MB | 0 B | | `grapheme-splitter` | 570 kB | 0 B | | `@formatjs/intl-segmenter` | 1.95 MB | 0 B | diff --git a/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-bun_1.3.14.txt b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-bun_1.3.14.txt new file mode 100644 index 0000000..944be63 --- /dev/null +++ b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-bun_1.3.14.txt @@ -0,0 +1,265 @@ +clk: ~4.15 GHz +cpu: Apple M4 Pro +runtime: bun 1.3.14 (arm64-darwin) + +benchmark avg (min … max) p75 / p99 (min … top 1%) +------------------------------------------------------------- ------------------------------- +• Lorem ipsum (ascii) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.04 µs/iter 2.04 µs ▆█ + (2.00 µs … 2.32 µs) 2.15 µs ▅▇██▇▆▄▃▆▄▂▂▁▁▁▁▁▁▁▁▁ + gc(594.54 µs … 898.75 µs) 105.56 b ( 0.00 b… 15.49 kb) + +graphemer 17.83 µs/iter 18.25 µs █▂ + (15.71 µs … 60.46 µs) 30.38 µs ▃██▄▅▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ + gc(292.50 µs … 17.05 ms) 90.56 b ( 0.00 b… 96.00 kb) + +grapheme-splitter 69.51 µs/iter 70.13 µs ██▃▂ + (52.00 µs … 128.54 µs) 123.42 µs ▃█████▄▃▂▁▃▂▂▂▁▂▂▂▁▃▂ + gc(313.58 µs … 320.61 ms) 3.47 kb ( 0.00 b…128.00 kb) + +@formatjs/intl-segmenter 56.55 µs/iter 58.50 µs ▄█ ▅▄ + (49.29 µs … 101.17 µs) 79.63 µs ▃██████▅▆▃▃▂▂▂▂▂▁▁▁▁▁ + gc(311.00 µs … 134.75 ms) 1.23 kb ( 0.00 b…752.00 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 12.64 µs/iter 12.71 µs ▇█▄ + (10.50 µs … 54.83 µs) 24.92 µs ▄███▅▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ + gc(313.88 µs … 691.83 µs) 50.46 b ( 0.00 b… 16.00 kb) + +Intl.Segmenter 4.15 µs/iter 4.22 µs ▅ █ ▅ + (3.98 µs … 4.34 µs) 4.33 µs ▄▄▄▆▄▄████▆▃▄██▆▄▄▄▁▄ + gc( 1.70 ms … 2.39 ms) 65.26 b ( 0.00 b… 4.63 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.04 µs + graphemer ┤■■■■■■■■ 17.83 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 69.51 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 56.55 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 12.64 µs + Intl.Segmenter ┤■ 4.15 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.04x faster than Intl.Segmenter + 6.2x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 8.75x faster than graphemer + 27.75x faster than @formatjs/intl-segmenter + 34.11x faster than grapheme-splitter + +• Emojis +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 824.86 ns/iter 834.07 ns █▄▄ ▂ + (791.15 ns … 880.29 ns) 871.35 ns ▂▃▄▅▇███████▇▄▆▂▃▂▂▁▂ + gc(435.58 µs … 809.13 µs) 0.07 b ( 0.00 b… 20.00 b) + +graphemer 4.96 µs/iter 4.99 µs ▂ ▂▆█ █ ▆█▄ + (4.87 µs … 5.13 µs) 5.12 µs ▃█▃███▇█▅███▅▇▁▁▁▁▃▃▃ + gc(701.38 µs … 1.08 ms) 52.67 b ( 0.00 b… 3.35 kb) + +grapheme-splitter 17.56 µs/iter 17.67 µs ▃ ▃ █ ▃ + (17.29 µs … 17.95 µs) 17.95 µs █▁█▆▆▆▁█▁▁▁█▆▁▁▁▁▁▁▆▆ + gc(703.17 µs … 856.83 µs) 0.00 b ( 0.00 b… 0.00 b) + +@formatjs/intl-segmenter 20.21 µs/iter 20.52 µs █ █ █ + (19.76 µs … 20.79 µs) 20.67 µs █▁█▁▁███▁▁▁██▁▁▁▁█▁▁█ + gc( 1.81 ms … 2.05 ms) 57.41 b ( 0.00 b…976.00 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 6.21 µs/iter 6.29 µs █ + (5.17 µs … 22.67 µs) 11.67 µs ▄███▄▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁ + gc(327.63 µs … 679.13 µs) 0.00 b ( 0.00 b… 0.00 b) + +Intl.Segmenter 1.54 µs/iter 1.56 µs ▂▂▂ ▇▅█ ▂ + (1.50 µs … 1.64 µs) 1.63 µs ▇██████████▇▄▃▅▃▃▂▁▂▂ + gc( 1.49 ms … 2.11 ms) 0.00 b ( 0.00 b… 0.00 b) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 824.86 ns + graphemer ┤■■■■■■■ 4.96 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 17.56 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 20.21 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■ 6.21 µs + Intl.Segmenter ┤■ 1.54 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.87x faster than Intl.Segmenter + 6.02x faster than graphemer + 7.52x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 21.29x faster than grapheme-splitter + 24.51x faster than @formatjs/intl-segmenter + +• Hindi +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.55 µs/iter 2.58 µs ▆ ▅▅ █▅▃ ▅▅█▃ + (2.47 µs … 2.64 µs) 2.62 µs █▄██▆██████████████▃▃ + gc( 1.09 ms … 1.46 ms) 0.03 b ( 0.00 b… 4.00 b) + +graphemer 17.60 µs/iter 17.68 µs █ ██ + (17.38 µs … 17.87 µs) 17.75 µs █▁█▁▁██▁█▁█▁▁█▁███▁██ + gc( 1.06 ms … 1.28 ms) 43.00 b ( 0.00 b…624.00 b) + +grapheme-splitter 34.87 µs/iter 35.21 µs █ █ + (33.96 µs … 36.65 µs) 35.55 µs ██▁▁▁▁█▁█▁▁█▁▁▁██▁█▁█ + gc( 1.10 ms … 1.26 ms) 0.00 b ( 0.00 b… 0.00 b) + +@formatjs/intl-segmenter 63.70 µs/iter 64.59 µs █ + (62.54 µs … 65.32 µs) 64.91 µs █▁█▁█▁███▁▁▁█▁▁▁▁██▁█ + gc( 2.48 ms … 2.65 ms) 30.18 b ( 0.00 b…328.00 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 10.97 µs/iter 11.01 µs █▄ + (10.85 µs … 11.15 µs) 11.12 µs ▅▅▁██▅██▁█▅▅▅▅█▁▁▁▁▅▅ + gc( 4.04 ms … 5.07 ms) 133.33 b ( 0.00 b… 3.50 kb) + +Intl.Segmenter 3.60 µs/iter 3.64 µs █ + (3.48 µs … 3.78 µs) 3.73 µs ▂▄▅▂▅▃▄█▆▂▅▆▅▆▂▂▃▆▃▁▃ + gc( 1.63 ms … 2.12 ms) 0.05 b ( 0.00 b… 4.00 b) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.55 µs + graphemer ┤■■■■■■■■ 17.60 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■ 34.87 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 63.70 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 10.97 µs + Intl.Segmenter ┤■ 3.60 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.41x faster than Intl.Segmenter + 4.31x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 6.91x faster than graphemer + 13.69x faster than grapheme-splitter + 25.01x faster than @formatjs/intl-segmenter + +• Demonic characters +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 441.81 ns/iter 447.14 ns ██▅▃▄ ▂ + (423.29 ns … 475.70 ns) 467.44 ns ▁▂▂▇██████▇█▄▇▅▅▂▃▂▂▁ + gc(367.88 µs … 589.58 µs) 0.00 b ( 0.00 b… 0.00 b) + +graphemer 7.97 µs/iter 8.02 µs ▂ █ ▂ ▂ + (7.83 µs … 8.18 µs) 8.12 µs ▄▄█▄▄▇█▇█▇▄▄▇█▇▇▁▁▇▇▄ + gc(806.00 µs … 1.00 ms) 11.43 b ( 0.00 b…476.00 b) + +grapheme-splitter 7.16 µs/iter 7.21 µs ▂ ▂ █▂ ▂ + (7.04 µs … 7.37 µs) 7.34 µs ▇▃█▅█▃▃██▇▃▁█▃▁▇▁▁▃▃▃ + gc(820.08 µs … 1.13 ms) 0.00 b ( 0.00 b… 0.00 b) + +@formatjs/intl-segmenter 73.10 µs/iter 76.50 µs █ ▃ ▃ + (65.50 µs … 96.71 µs) 86.67 µs ▃█▇▄▅█▆▄███▇▆▄▃▃▃▂▁▂▁ + gc(337.25 µs … 714.38 µs) 0.00 b ( 0.00 b… 0.00 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 2.15 µs/iter 2.18 µs ▆▃█▇▃▂▆▄▄▄ █▂ + (2.09 µs … 2.25 µs) 2.24 µs ▄██████████▇▇██▇▅▂▄▂▂ + gc(696.17 µs … 951.50 µs) 0.25 b ( 0.00 b… 36.00 b) + +Intl.Segmenter 717.64 ns/iter 726.52 ns ▄▄█▆▃ + (681.83 ns … 848.61 ns) 806.65 ns ▃███████▇▃▅▂▃▂▃▁▃▂▂▁▁ + gc( 1.24 ms … 2.05 ms) 0.03 b ( 0.00 b… 4.00 b) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 441.81 ns + graphemer ┤■■■■ 7.97 µs + grapheme-splitter ┤■■■ 7.16 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 73.10 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■ 2.15 µs + Intl.Segmenter ┤ 717.64 ns + └ ┘ + +summary + unicode-segmenter/grapheme + 1.62x faster than Intl.Segmenter + 4.87x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 16.2x faster than grapheme-splitter + 18.04x faster than graphemer + 165.45x faster than @formatjs/intl-segmenter + +• Tweet text (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 4.16 µs/iter 4.17 µs ▃▃▃ █ + (4.09 µs … 4.42 µs) 4.30 µs ▄███▂▄█▆▄▇▂▁▁▂▄▃▁▃▁▁▂ + gc( 1.52 ms … 1.84 ms) 0.00 b ( 0.00 b… 0.00 b) + +graphemer 25.03 µs/iter 25.05 µs █ █ + (24.88 µs … 25.30 µs) 25.24 µs █▁▁██▁▁█▁█▁█▁▁▁▁▁▁▁▁█ + gc( 1.05 ms … 1.19 ms) 2.29 b ( 0.00 b… 32.00 b) + +grapheme-splitter 56.23 µs/iter 56.66 µs █ + (55.12 µs … 58.37 µs) 57.28 µs ██▁▁██▁▁███▁▁▁██▁▁▁▁█ + gc( 1.15 ms … 1.37 ms) 0.00 b ( 0.00 b… 0.00 b) + +@formatjs/intl-segmenter 84.09 µs/iter 87.04 µs ▆█▂ + (72.79 µs … 106.58 µs) 97.25 µs ▂▃▄▇▅▅▅▆▅█████▄▃▂▂▁▁▁ + gc(344.83 µs … 662.96 µs) 0.00 b ( 0.00 b… 0.00 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 15.64 µs/iter 15.66 µs ██ █ + (15.49 µs … 15.82 µs) 15.81 µs █▁█▁████████▁▁█▁▁▁█▁█ + gc( 5.14 ms … 5.58 ms) 48.95 b ( 0.00 b… 1.00 kb) + +Intl.Segmenter 6.20 µs/iter 6.24 µs █ █ ▂ + (6.09 µs … 6.41 µs) 6.40 µs ▇▃▅█▅▅▅██▅█▇▁▁▅▁▁▁▁▃▃ + gc( 1.92 ms … 2.71 ms) 5.02 b ( 0.00 b…256.00 b) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 4.16 µs + graphemer ┤■■■■■■■■■ 25.03 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■ 56.23 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 84.09 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 15.64 µs + Intl.Segmenter ┤■ 6.20 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.49x faster than Intl.Segmenter + 3.76x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 6.02x faster than graphemer + 13.53x faster than grapheme-splitter + 20.23x faster than @formatjs/intl-segmenter + +• Code snippet (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 10.14 µs/iter 10.16 µs ▃▃ ▃ █ + (10.05 µs … 10.47 µs) 10.30 µs ██▁█▄▁▄█▄▄▄▁▁▆▁▁▁▁▁▄▆ + gc( 1.82 ms … 2.47 ms) 126.79 b ( 0.00 b… 4.04 kb) + +graphemer 58.94 µs/iter 59.20 µs █ █ + (57.99 µs … 61.49 µs) 59.61 µs █▁▁▁▁██▁██▁▁▁█▁█▁█▁▁█ + gc( 1.20 ms … 1.34 ms) 2.00 b ( 0.00 b… 24.00 b) + +grapheme-splitter 130.20 µs/iter 136.46 µs █▅ + (120.46 µs … 173.88 µs) 152.33 µs ▄████▆▄▃▄▅█▇▅▄▃▃▂▂▂▁▁ + gc(350.58 µs … 1.12 ms) 0.00 b ( 0.00 b… 0.00 b) + +@formatjs/intl-segmenter 182.92 µs/iter 189.00 µs ▃▅▄█▆▃▂ + (161.88 µs … 215.04 µs) 205.50 µs ▂▃▃▆▇██████████▇▅▃▃▂▁ + gc(353.96 µs … 751.71 µs) 0.00 b ( 0.00 b… 0.00 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 37.69 µs/iter 37.76 µs █ + (36.98 µs … 39.43 µs) 38.07 µs ██▁▁▁█▁▁██▁██▁█▁▁█▁▁█ + gc( 5.79 ms … 6.39 ms) 206.00 b ( 0.00 b… 2.40 kb) + +Intl.Segmenter 14.75 µs/iter 14.83 µs ▃ █▃ + (14.59 µs … 14.99 µs) 14.99 µs █▆▁▆▆██▆▆▆▁▁▆▆▁▆▁▆▁▁▆ + gc( 1.53 ms … 1.95 ms) 21.04 b ( 0.00 b…484.00 b) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 10.14 µs + graphemer ┤■■■■■■■■■■ 58.94 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■ 130.20 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 182.92 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 37.69 µs + Intl.Segmenter ┤■ 14.75 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.45x faster than Intl.Segmenter + 3.72x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 5.81x faster than graphemer + 12.84x faster than grapheme-splitter + 18.04x faster than @formatjs/intl-segmenter + +! = run with sudo to enable hardware counters diff --git a/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-chrome_150.0.7871.186.txt b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-chrome_150.0.7871.186.txt new file mode 100644 index 0000000..c089310 --- /dev/null +++ b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-chrome_150.0.7871.186.txt @@ -0,0 +1,197 @@ +clk: ~4.19 GHz +cpu: null +runtime: null (null) + +benchmark avg (min … max) p75 / p99 (min … top 1%) +------------------------------------------------------------- ------------------------------- +• Lorem ipsum (ascii) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.60 µs/iter 2.61 µs ▆ █ + (2.54 µs … 2.71 µs) 2.69 µs ▆▁▁█▁▁▁█▁▁█▁▁▇▁▁▁▅▁▁▂ +graphemer 65.62 µs/iter 100.00 µs █ + (0.00 ps … 400.00 µs) 300.00 µs █▁▁▁▁▁▁█▁▁▁▁▁▃▁▁▁▁▁▁▃ +grapheme-splitter 69.30 µs/iter 100.00 µs █ ▄ + (0.00 ps … 800.00 µs) 300.00 µs █▁▁▁▁▁▁█▁▁▁▁▁▃▁▁▁▁▁▁▂ +@formatjs/intl-segmenter 74.14 µs/iter 100.00 µs █ ▃ + (0.00 ps … 400.00 µs) 300.00 µs █▁▁▁▁▁▁█▁▁▁▁▁▄▁▁▁▁▁▁▃ +unicode-rs/unicode-segmentation (wasm-bindgen) 21.52 µs/iter 0.00 ps █ + (0.00 ps … 300.00 µs) 100.00 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅ +Intl.Segmenter 5.24 µs/iter 5.22 µs ▂█▇ + (5.05 µs … 6.01 µs) 5.96 µs ▂███▆▂▂▂▁▁▂▁▁▁▁▁▁▁▁▃▂ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.60 µs + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 65.62 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 69.30 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 74.14 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■ 21.52 µs + Intl.Segmenter ┤■ 5.24 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.01x faster than Intl.Segmenter + 8.27x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 25.22x faster than graphemer + 26.63x faster than grapheme-splitter + 28.5x faster than @formatjs/intl-segmenter + +• Emojis +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 829.68 ns/iter 878.91 ns █ ▄ + (732.42 ns … 952.15 ns) 927.73 ns ▁▁█▃▁█▁▃▃▁▁▁▆▁▁█▁▅▄▁▄ +graphemer 6.61 µs/iter 6.64 µs █ ▄ █ ▄ ▆ + (6.52 µs … 6.79 µs) 6.74 µs ▃▁█▁█▁▁█▁█▁█▁▁▁▁▅▁▅▁▇ +grapheme-splitter 19.62 µs/iter 0.00 ps █ + (0.00 ps … 300.00 µs) 100.00 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄ +@formatjs/intl-segmenter 11.55 µs/iter 0.00 ps █ + (0.00 ps … 300.00 µs) 100.00 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ +unicode-rs/unicode-segmentation (wasm-bindgen) 4.66 µs/iter 4.71 µs █▅ ▃ + (4.49 µs … 4.93 µs) 4.86 µs ▂▂▁█▄█▁██▆▁██▂▁▄▅▂▁▄▂ +Intl.Segmenter 2.34 µs/iter 2.34 µs █ ▅ + (2.27 µs … 2.54 µs) 2.44 µs ▃▁▁█▁▁█▁▁█▁▄▁▁▃▁▁▃▁▁▂ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 829.68 ns + graphemer ┤■■■■■■■■■■ 6.61 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 19.62 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■ 11.55 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■ 4.66 µs + Intl.Segmenter ┤■■■ 2.34 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.82x faster than Intl.Segmenter + 5.62x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 7.97x faster than graphemer + 13.92x faster than @formatjs/intl-segmenter + 23.64x faster than grapheme-splitter + +• Hindi +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.43 µs/iter 2.47 µs ▃ ▃ ▆ █ + (2.34 µs … 2.59 µs) 2.59 µs ▆▁█▁█▁█▁█▁█▁▅▁▃▁▃▁▅▁▂ +graphemer 24.71 µs/iter 24.68 µs ▃ █ ▃ + (24.63 µs … 25.02 µs) 24.73 µs █▁▁▁▁█▁▁▁▁█▁▁▁▁▁▁▁▁▁▆ +grapheme-splitter 67.34 µs/iter 100.00 µs ▇ █ + (0.00 ps … 500.00 µs) 300.00 µs █▁▁▁▁▁▁█▁▁▁▁▁▂▁▁▁▁▁▁▂ +@formatjs/intl-segmenter 30.93 µs/iter 30.91 µs █ ██ + (30.69 µs … 31.57 µs) 31.32 µs █▁████▁█▁▁▁▁▁▁▁█▁▁▁▁█ +unicode-rs/unicode-segmentation (wasm-bindgen) 32.40 µs/iter 100.00 µs █ + (0.00 ps … 500.00 µs) 300.00 µs █▁▁▁▁▁▁▆▁▁▁▁▁▁▁▁▁▁▁▁▁ +Intl.Segmenter 5.23 µs/iter 5.25 µs █▇▂ + (5.13 µs … 5.66 µs) 5.59 µs ▂▆███▇▄▁▅▁▁▁▁▁▁▁▁▁▁▂▂ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.43 µs + graphemer ┤■■■■■■■■■■■■ 24.71 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 67.34 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■ 30.93 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■■■■■■■■ 32.40 µs + Intl.Segmenter ┤■ 5.23 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.15x faster than Intl.Segmenter + 10.15x faster than graphemer + 12.71x faster than @formatjs/intl-segmenter + 13.31x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 27.67x faster than grapheme-splitter + +• Demonic characters +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 476.73 ns/iter 537.11 ns ▅▂ ▆ ▃ █ + (390.62 ns … 610.35 ns) 585.94 ns ▅▁██▁█▁▁▂▁▃▁█▃▁█▁▄▅▁▂ +graphemer 13.23 µs/iter 13.31 µs ▄ █ ▄ ▄ + (13.13 µs … 13.38 µs) 13.33 µs █▁█▁▁█▁█▁▁▅▁▅▁▁▁▁█▁▁█ +grapheme-splitter 10.31 µs/iter 10.42 µs █ █ █ ▃▃▃ ▃ + (10.03 µs … 10.67 µs) 10.64 µs ▆█▆█▁▁█▁███▁█▆▆▆▆▁▆▆▆ +@formatjs/intl-segmenter 11.79 µs/iter 11.84 µs ▄ █ █ + (11.67 µs … 12.01 µs) 11.96 µs █▁█▅▁█▁█▅▁█▁█▅▁▅▁▁▁▁▅ +unicode-rs/unicode-segmentation (wasm-bindgen) 2.38 µs/iter 2.42 µs ▄▄ █ ▇ ▂ + (2.27 µs … 2.61 µs) 2.54 µs ▅▁▇▁██▁█▁█▁█▁█▁▆▅▁▁▁▂ +Intl.Segmenter 1.30 µs/iter 1.32 µs █ ▄ ▃ + (1.25 µs … 1.44 µs) 1.39 µs █▁▁█▁▁▁█▁▁█▁▁▇▁▁▁▃▁▁▄ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 476.73 ns + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 13.23 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 10.31 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 11.79 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 2.38 µs + Intl.Segmenter ┤■■ 1.30 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.73x faster than Intl.Segmenter + 4.99x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 21.63x faster than grapheme-splitter + 24.74x faster than @formatjs/intl-segmenter + 27.76x faster than graphemer + +• Tweet text (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 3.80 µs/iter 3.81 µs █ ▃ + (3.76 µs … 3.96 µs) 3.91 µs █▁▁█▁▁▁█▁▁▄▁▁▂▁▁▁▃▁▁▃ +graphemer 89.59 µs/iter 100.00 µs █ ▆ + (0.00 ps … 500.00 µs) 400.00 µs █▁▁▁▁█▁▁▁▁▃▁▁▁▁▄▁▁▁▁▁ +grapheme-splitter 119.21 µs/iter 200.00 µs █ + (0.00 ps … 400.00 µs) 200.00 µs ▃▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ +@formatjs/intl-segmenter 47.43 µs/iter 47.49 µs █ ▃ + (47.00 µs … 48.32 µs) 47.97 µs ▆▁▁▆█▁▆▁▆▆█▁▁▁▁▁▁▁▁▁▆ +unicode-rs/unicode-segmentation (wasm-bindgen) 21.39 µs/iter 21.39 µs █ █ ▃ + (21.34 µs … 21.53 µs) 21.48 µs █▁▁█▁▁▁█▁▁▁▁▁▆▁▁▁▁▁▁▆ +Intl.Segmenter 9.15 µs/iter 9.18 µs █ ▅ ▅ ▅ ▅ ▂ + (9.06 µs … 9.28 µs) 9.28 µs ▇▁█▁█▁▁█▁█▁█▁▇▁▁▄▁█▁▄ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 3.80 µs + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■■■■ 89.59 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 119.21 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■ 47.43 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 21.39 µs + Intl.Segmenter ┤■■ 9.15 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.41x faster than Intl.Segmenter + 5.63x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 12.48x faster than @formatjs/intl-segmenter + 23.56x faster than graphemer + 31.36x faster than grapheme-splitter + +• Code snippet (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 9.30 µs/iter 9.30 µs ▂ ▆ █ + (9.20 µs … 9.52 µs) 9.42 µs ▇▁▅▁█▁▁█▁█▁▃▁▅▁▁▅▁▃▁▃ +graphemer 201.78 µs/iter 300.00 µs █ + (0.00 ps … 500.00 µs) 500.00 µs ▄▁▁▁█▁▁▁▇▁▁▁▇▁▁▁█▁▁▁▁ +grapheme-splitter 307.69 µs/iter 400.00 µs ▃ █ + (100.00 µs … 600.00 µs) 600.00 µs ▂▁▁▁█▁▁▁█▁▁▁▅▁▁▁▅▁▁▁▁ +@formatjs/intl-segmenter 156.04 µs/iter 200.00 µs █ ▂ + (0.00 ps … 600.00 µs) 500.00 µs ▃▁▁▁█▁▁▁█▁▁▁▂▁▁▁▃▁▁▁▁ +unicode-rs/unicode-segmentation (wasm-bindgen) 49.54 µs/iter 49.76 µs █ █ + (48.85 µs … 50.02 µs) 49.85 µs █▁▁▁▁▁▁▁█▁▁██▁██▁▁███ +Intl.Segmenter 18.21 µs/iter 18.21 µs █ █ + (18.14 µs … 18.43 µs) 18.33 µs █▁▅▁▁▅▁▁█▁█▁▁▁▁▁▁▁▁▁▅ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 9.30 µs + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■ 201.78 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 307.69 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■ 156.04 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 49.54 µs + Intl.Segmenter ┤■ 18.21 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.96x faster than Intl.Segmenter + 5.33x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 16.78x faster than @formatjs/intl-segmenter + 21.7x faster than graphemer + 33.09x faster than grapheme-splitter diff --git a/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-firefox_154.0b1.txt b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-firefox_154.0b1.txt new file mode 100644 index 0000000..4f22ac1 --- /dev/null +++ b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-firefox_154.0b1.txt @@ -0,0 +1,197 @@ +clk: ~3.90 GHz +cpu: null +runtime: null (null) + +benchmark avg (min … max) p75 / p99 (min … top 1%) +------------------------------------------------------------- ------------------------------- +• Lorem ipsum (ascii) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 5.53 µs/iter 5.62 µs █ ▇ + (5.37 µs … 5.86 µs) 5.86 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▄ +graphemer 19.95 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +grapheme-splitter 40.53 µs/iter 0.00 ps █ + (0.00 ps … 2.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +@formatjs/intl-segmenter 32.45 µs/iter 0.00 ps █ + (0.00 ps … 5.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +unicode-rs/unicode-segmentation (wasm-bindgen) 14.96 µs/iter 15.14 µs ▃ █ ▆ + (14.65 µs … 15.38 µs) 15.38 µs █▁▁▁▁▁▁█▁▁▁▁▁█▁▁▁▁▁▁▄ +Intl.Segmenter 3.11 µs/iter 3.17 µs ▃ █ + (2.93 µs … 3.91 µs) 3.42 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▃ + + ┌ ┐ + unicode-segmenter/grapheme ┤■■ 5.53 µs + graphemer ┤■■■■■■■■■■■■■■■ 19.95 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 40.53 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 32.45 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■■■ 14.96 µs + Intl.Segmenter ┤ 3.11 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.78x slower than Intl.Segmenter + 2.7x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 3.61x faster than graphemer + 5.87x faster than @formatjs/intl-segmenter + 7.33x faster than grapheme-splitter + +• Emojis +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 1.61 µs/iter 1.71 µs ▆ █ + (1.46 µs … 1.95 µs) 1.95 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁ +graphemer 5.77 µs/iter 5.86 µs ▂ █ + (5.62 µs … 6.10 µs) 5.86 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +grapheme-splitter 24.70 µs/iter 0.00 ps █ + (0.00 ps … 2.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +@formatjs/intl-segmenter 9.63 µs/iter 9.77 µs █ ▄ + (9.52 µs … 9.77 µs) 9.77 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +unicode-rs/unicode-segmentation (wasm-bindgen) 4.62 µs/iter 4.64 µs █ + (4.39 µs … 4.88 µs) 4.88 µs ▄▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▂ +Intl.Segmenter 1.08 µs/iter 1.22 µs █ + (732.42 ns … 4.39 µs) 1.22 µs ▂▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ + + ┌ ┐ + unicode-segmenter/grapheme ┤■ 1.61 µs + graphemer ┤■■■■■■■ 5.77 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 24.70 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■ 9.63 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 4.62 µs + Intl.Segmenter ┤ 1.08 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.49x slower than Intl.Segmenter + 2.88x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 3.6x faster than graphemer + 6x faster than @formatjs/intl-segmenter + 15.38x faster than grapheme-splitter + +• Hindi +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 5.24 µs/iter 5.37 µs █ ▆ + (5.13 µs … 5.37 µs) 5.37 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +graphemer 19.80 µs/iter 19.78 µs █ + (19.53 µs … 20.02 µs) 20.02 µs ▅▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▅ +grapheme-splitter 52.96 µs/iter 54.20 µs █ █ █ + (47.36 µs … 55.91 µs) 54.69 µs █▁▁▁▁▁▁▁▁▁▁▁███▁██▁██ +@formatjs/intl-segmenter 32.39 µs/iter 32.47 µs ▂ █ + (31.98 µs … 33.20 µs) 32.71 µs █▁▁▁▁▁▁▇▁▁▁▁▁█▁▁▁▁▁▁▄ +unicode-rs/unicode-segmentation (wasm-bindgen) 14.73 µs/iter 14.89 µs █ ▂ + (14.40 µs … 14.89 µs) 14.89 µs ▃▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ +Intl.Segmenter 2.65 µs/iter 2.69 µs █ + (2.44 µs … 2.93 µs) 2.93 µs ▄▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▂ + + ┌ ┐ + unicode-segmenter/grapheme ┤■■ 5.24 µs + graphemer ┤■■■■■■■■■■■■ 19.80 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 52.96 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■ 32.39 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■ 14.73 µs + Intl.Segmenter ┤ 2.65 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.97x slower than Intl.Segmenter + 2.81x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 3.78x faster than graphemer + 6.18x faster than @formatjs/intl-segmenter + 10.11x faster than grapheme-splitter + +• Demonic characters +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 858.56 ns/iter 976.56 ns ▅ █ + (488.28 ns … 1.22 µs) 1.22 µs ▂▁▁▁▁▁▁█▁▁▁▁▁█▁▁▁▁▁▁▁ +graphemer 9.38 µs/iter 9.52 µs █ + (9.28 µs … 9.77 µs) 9.52 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +grapheme-splitter 8.27 µs/iter 8.30 µs █ + (8.06 µs … 8.54 µs) 8.54 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▄ +@formatjs/intl-segmenter 11.91 µs/iter 11.96 µs ▃ █ + (11.72 µs … 12.21 µs) 12.21 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▄ +unicode-rs/unicode-segmentation (wasm-bindgen) 2.53 µs/iter 2.69 µs █ ▄ + (2.20 µs … 2.69 µs) 2.69 µs ▃▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ +Intl.Segmenter 446.94 ns/iter 488.28 ns ▃ █ + (0.00 ps … 4.15 µs) 3.91 µs ▁█▁█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ┌ ┐ + unicode-segmenter/grapheme ┤■ 858.56 ns + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 9.38 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■ 8.27 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 11.91 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■ 2.53 µs + Intl.Segmenter ┤ 446.94 ns + └ ┘ + +summary + unicode-segmenter/grapheme + 1.92x slower than Intl.Segmenter + 2.94x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 9.63x faster than grapheme-splitter + 10.92x faster than graphemer + 13.87x faster than @formatjs/intl-segmenter + +• Tweet text (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 8.11 µs/iter 8.06 µs █ + (8.06 µs … 8.30 µs) 8.30 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅ +graphemer 29.74 µs/iter 29.79 µs ▄ █ + (29.30 µs … 30.52 µs) 30.03 µs █▁▁▁▁▁▁█▁▁▁▁▁█▁▁▁▁▁▁█ +grapheme-splitter 100.08 µs/iter 103.76 µs █ + (93.75 µs … 109.13 µs) 108.89 µs ██▁███▁██▁▁▁▁█▁▁█▁▁▁█ +@formatjs/intl-segmenter 44.29 µs/iter 44.68 µs █ █ █ █ + (43.21 µs … 45.17 µs) 45.17 µs █▁▁▁▁▁▁▁█▁█▁▁█▁█▁▁█▁█ +unicode-rs/unicode-segmentation (wasm-bindgen) 22.71 µs/iter 22.71 µs █ █ ▃ + (22.22 µs … 23.44 µs) 22.95 µs ▆▁▁▁▁▁▁█▁▁▁▁▁█▁▁▁▁▁▁█ +Intl.Segmenter 4.52 µs/iter 4.64 µs █ █ + (4.39 µs … 4.64 µs) 4.64 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ + + ┌ ┐ + unicode-segmenter/grapheme ┤■ 8.11 µs + graphemer ┤■■■■■■■■■ 29.74 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 100.08 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■ 44.29 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■ 22.71 µs + Intl.Segmenter ┤ 4.52 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.8x slower than Intl.Segmenter + 2.8x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 3.67x faster than graphemer + 5.46x faster than @formatjs/intl-segmenter + 12.34x faster than grapheme-splitter + +• Code snippet (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 19.36 µs/iter 19.53 µs █ █ + (19.04 µs … 19.53 µs) 19.53 µs ▇▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ +graphemer 68.83 µs/iter 68.85 µs █ + (68.12 µs … 70.07 µs) 69.34 µs ▅▁▁▁█▁▁▁█▁▁▁█▁▁▁▅▁▁▁▅ +grapheme-splitter 218.20 µs/iter 220.95 µs █ + (213.13 µs … 224.85 µs) 223.14 µs █▁▁██▁█▁█▁██▁▁▁▁██▁▁█ +@formatjs/intl-segmenter 102.17 µs/iter 103.03 µs █ █ █ + (100.59 µs … 103.52 µs) 103.27 µs █▁▁▁▁█▁█▁█▁▁▁█▁▁█▁█▁█ +unicode-rs/unicode-segmentation (wasm-bindgen) 53.30 µs/iter 0.00 ps █ + (0.00 ps … 2.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂ +Intl.Segmenter 10.35 µs/iter 10.50 µs █ + (10.25 µs … 10.74 µs) 10.50 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ + + ┌ ┐ + unicode-segmenter/grapheme ┤■ 19.36 µs + graphemer ┤■■■■■■■■■■ 68.83 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 218.20 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■ 102.17 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■ 53.30 µs + Intl.Segmenter ┤ 10.35 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.87x slower than Intl.Segmenter + 2.75x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 3.55x faster than graphemer + 5.28x faster than @formatjs/intl-segmenter + 11.27x faster than grapheme-splitter diff --git a/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-nodejs_26.5.0.txt b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-nodejs_26.5.0.txt new file mode 100644 index 0000000..f5768df --- /dev/null +++ b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-nodejs_26.5.0.txt @@ -0,0 +1,265 @@ +clk: ~4.43 GHz +cpu: Apple M4 Pro +runtime: node 26.5.0 (arm64-darwin) + +benchmark avg (min … max) p75 / p99 (min … top 1%) +------------------------------------------------------------- ------------------------------- +• Lorem ipsum (ascii) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.65 µs/iter 2.67 µs ▂ ▃██ ▃ + (2.56 µs … 2.86 µs) 2.83 µs ▅▅█████▂█▇▆▃▄▄▄▂▂▁▁▁▂ + gc(717.38 µs … 1.58 ms) 881.87 b (864.51 b…954.84 b) + +graphemer 41.61 µs/iter 41.63 µs █ + (31.75 µs … 97.83 µs) 55.88 µs ▁▁▁▁▂▁▅█▇▃▂▂▂▂▁▁▁▁▁▁▁ + gc(778.67 µs … 1.60 ms) 81.71 kb ( 16.27 kb…444.19 kb) + +grapheme-splitter 69.07 µs/iter 57.25 µs █ + (48.50 µs … 659.21 µs) 605.88 µs █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + gc(772.00 µs … 1.72 ms) 83.12 kb ( 26.08 kb… 1.96 mb) + +@formatjs/intl-segmenter 32.22 µs/iter 32.32 µs █ + (31.84 µs … 32.66 µs) 32.53 µs █▁█▁▁███▁▁▁██▁█▁▁▁█▁█ + gc(866.88 µs … 1.49 ms) 2.80 kb ( 2.63 kb… 4.71 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 20.63 µs/iter 21.42 µs ▅█ ▂ + (15.79 µs … 75.83 µs) 37.46 µs ▁▅████▅▃▃▂▁▂▁▁▁▁▁▁▁▁▁ + gc(720.33 µs … 1.59 ms) 22.42 kb (888.00 b… 61.86 kb) + +Intl.Segmenter 5.30 µs/iter 5.32 µs ▃▆█▅ ▃ + (5.22 µs … 5.59 µs) 5.59 µs ▆███████▁▁▃▃▃▁▁▁▁▁▁▁▃ + gc( 2.36 ms … 4.01 ms) 5.92 kb ( 5.90 kb… 5.93 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.65 µs + graphemer ┤■■■■■■■■■■■■■■■■■■■■ 41.61 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 69.07 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■ 32.22 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■ 20.63 µs + Intl.Segmenter ┤■ 5.30 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2x faster than Intl.Segmenter + 7.79x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 12.17x faster than @formatjs/intl-segmenter + 15.71x faster than graphemer + 26.08x faster than grapheme-splitter + +• Emojis +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 829.70 ns/iter 839.14 ns ▇█▆▃▂▂ + (789.99 ns … 1.13 µs) 910.24 ns ▃▆███████▆▅▄▃▂▂▃▃▂▁▁▁ + gc(722.21 µs … 1.74 ms) 5.27 kb ( 5.24 kb… 5.28 kb) + +graphemer 7.60 µs/iter 7.63 µs ▃ █ + (7.49 µs … 7.77 µs) 7.76 µs ▄█▄█▆▄██▆██▄▄█▄▄▁▁▄▁▄ + gc(837.54 µs … 1.43 ms) 7.66 kb ( 7.61 kb… 7.68 kb) + +grapheme-splitter 43.18 µs/iter 28.46 µs █ + (22.04 µs … 320.38 µs) 287.88 µs █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▁ + gc(757.63 µs … 1.62 ms) 42.26 kb ( 12.53 kb… 1.55 mb) + +@formatjs/intl-segmenter 10.19 µs/iter 10.30 µs █ + (9.99 µs … 10.62 µs) 10.50 µs ▅▅▃▃█▃▅▅▅▁▁▁▅▁▁▃▇▃▁▁▃ + gc(872.88 µs … 1.45 ms) 4.08 kb ( 4.05 kb… 4.08 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 4.39 µs/iter 4.43 µs ██▆▆█▆▆ ▃ + (4.22 µs … 4.54 µs) 4.53 µs ▄▁▄▆██████████▄▆███▆▄ + gc(761.42 µs … 1.32 ms) 4.93 kb ( 4.77 kb… 5.39 kb) + +Intl.Segmenter 2.24 µs/iter 2.25 µs ▂█ ▄ + (2.19 µs … 2.33 µs) 2.31 µs ▇▄▆▅███▆█▇▇█▅▆▃▁▄▆▂▃▂ + gc( 2.24 ms … 3.52 ms) 4.45 kb ( 4.44 kb… 4.45 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 829.70 ns + graphemer ┤■■■■■ 7.60 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 43.18 µs + @formatjs/intl-segmenter ┤■■■■■■■■ 10.19 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■ 4.39 µs + Intl.Segmenter ┤■ 2.24 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.7x faster than Intl.Segmenter + 5.29x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 9.17x faster than graphemer + 12.28x faster than @formatjs/intl-segmenter + 52.05x faster than grapheme-splitter + +• Hindi +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.75 µs/iter 2.79 µs ▅▅▆▃▂█ + (2.60 µs … 2.87 µs) 2.83 µs ▄▂█▂▆▂▃▂▂▃▂███████▃█▆ + gc(780.29 µs … 1.40 ms) 6.26 kb ( 6.25 kb… 6.27 kb) + +graphemer 28.26 µs/iter 28.41 µs █ ██ █ ██ █ █ + (28.06 µs … 28.47 µs) 28.45 µs █▁▁▁██▁█▁██▁▁▁▁▁▁▁█▁█ + gc(883.04 µs … 1.42 ms) 7.56 kb ( 7.55 kb… 7.58 kb) + +grapheme-splitter 59.74 µs/iter 60.96 µs ▂▃█▃ + (53.42 µs … 92.92 µs) 78.67 µs ▂▃██████▃▂▂▂▁▁▁▁▁▁▁▁▁ + gc(782.21 µs … 1.34 ms) 50.99 kb ( 6.87 kb…136.87 kb) + +@formatjs/intl-segmenter 30.90 µs/iter 30.99 µs █ █ █ + (30.61 µs … 31.53 µs) 31.06 µs █▁▁▁█▁▁▁███▁█▁▁▁▁█▁▁█ + gc(884.67 µs … 1.25 ms) 7.40 kb ( 7.39 kb… 7.42 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 13.21 µs/iter 13.24 µs █ + (13.08 µs … 13.43 µs) 13.43 µs ▅█▅▅█▅█▅██▁▅▁▅▅▁▁▁▁▁▅ + gc(845.25 µs … 1.41 ms) 5.61 kb ( 5.59 kb… 5.61 kb) + +Intl.Segmenter 5.44 µs/iter 5.47 µs █▄▄█ ▂ + (5.29 µs … 5.63 µs) 5.61 µs ▃▁▅▃▁▃▇████▇█▁▇▃▁▃▁▁▃ + gc( 2.31 ms … 2.97 ms) 3.47 kb ( 3.45 kb… 3.47 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.75 µs + graphemer ┤■■■■■■■■■■■■■■■ 28.26 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 59.74 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■ 30.90 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■ 13.21 µs + Intl.Segmenter ┤■■ 5.44 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.98x faster than Intl.Segmenter + 4.81x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 10.29x faster than graphemer + 11.25x faster than @formatjs/intl-segmenter + 21.75x faster than grapheme-splitter + +• Demonic characters +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 457.21 ns/iter 460.80 ns ▃▆█▆▆ + (431.57 ns … 491.13 ns) 479.99 ns ▂▁▂▂▂▄▅▅██████▄▃▂▄▃▃▂ + gc(694.38 µs … 1.34 ms) 1.34 kb ( 1.32 kb… 1.35 kb) + +graphemer 15.15 µs/iter 15.19 µs ███ █ █ + (15.00 µs … 15.38 µs) 15.30 µs █▁█▁███████▁█▁█▁█▁▁▁█ + gc(862.58 µs … 1.28 ms) 4.79 kb ( 4.75 kb… 4.88 kb) + +grapheme-splitter 11.08 µs/iter 11.18 µs █ + (10.81 µs … 11.38 µs) 11.33 µs █▁▅▁▅▅█▅▅▅█▅█▅▅▁█▅▅▁█ + gc(839.92 µs … 1.47 ms) 3.21 kb ( 3.17 kb… 3.25 kb) + +@formatjs/intl-segmenter 11.87 µs/iter 11.91 µs ▃ ▃ ▃▃▃▃ █ + (11.75 µs … 12.09 µs) 11.97 µs ▆▁▆█▁▆█▁▆████▆█▆▁▁▆▁▆ + gc(840.25 µs … 1.45 ms) 2.56 kb ( 2.54 kb… 2.57 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 2.20 µs/iter 2.23 µs ▃ ▆▂█▂▆ ▇ + (2.14 µs … 2.32 µs) 2.31 µs ▃█▅██████▆█▆▅▅▄▂▃▁▂▄▂ + gc(708.71 µs … 1.69 ms) 1.28 kb ( 1.06 kb… 1.38 kb) + +Intl.Segmenter 1.05 µs/iter 1.07 µs ▇▄██▅▅▂▆ + (978.07 ns … 1.18 µs) 1.17 µs ▂▆██████████▆█▄▄▅▃▃▄▂ + gc( 2.15 ms … 3.04 ms) 1.23 kb ( 1.20 kb… 1.23 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 457.21 ns + graphemer ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 15.15 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■ 11.08 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 11.87 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■ 2.20 µs + Intl.Segmenter ┤■ 1.05 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.3x faster than Intl.Segmenter + 4.82x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 24.24x faster than grapheme-splitter + 25.95x faster than @formatjs/intl-segmenter + 33.14x faster than graphemer + +• Tweet text (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 4.12 µs/iter 4.15 µs ▃ ▃ █ + (4.03 µs … 4.22 µs) 4.22 µs ▃▃▆▄▆██████████▃▃▃▄▄▃ + gc(810.04 µs … 1.25 ms) 1.34 kb ( 1.32 kb… 1.34 kb) + +graphemer 54.78 µs/iter 55.71 µs █ + (44.71 µs … 82.83 µs) 71.54 µs ▁▁▁▁▅▇█▇█▄▃▂▂▂▁▂▂▂▁▁▁ + gc(741.75 µs … 1.49 ms) 114.20 kb ( 77.96 kb…241.96 kb) + +grapheme-splitter 108.15 µs/iter 109.71 µs █▃ + (94.08 µs … 137.71 µs) 127.21 µs ▁▁▁▁▁▂████▅▄▂▂▂▁▁▁▁▁▁ + gc(753.21 µs … 1.30 ms) 66.87 kb ( 63.05 kb…151.20 kb) + +@formatjs/intl-segmenter 47.46 µs/iter 47.56 µs █ ▃ ▃ + (47.12 µs … 47.84 µs) 47.70 µs ▆▁▁▁▁▁▆▁█▁▁█▁▁▆█▁▁▁▁▆ + gc(879.63 µs … 1.35 ms) 244.74 b (221.84 b…270.12 b) + +unicode-rs/unicode-segmentation (wasm-bindgen) 18.90 µs/iter 18.98 µs █ █ █ + (18.68 µs … 19.06 µs) 19.04 µs █▁▁██▁▁▁▁██▁█▁▁▁██▁██ + gc(854.42 µs … 1.28 ms) 162.16 b (145.73 b…168.67 b) + +Intl.Segmenter 8.53 µs/iter 8.55 µs █ ▂ ▂▂▂ ▂ + (8.41 µs … 8.78 µs) 8.74 µs ▇▁█▇█▇███▇█▇▁▁▁▄▄▁▁▁▄ + gc( 1.83 ms … 2.50 ms) 4.19 kb ( 4.18 kb… 4.19 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 4.12 µs + graphemer ┤■■■■■■■■■■■■■■■■■ 54.78 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 108.15 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■ 47.46 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 18.90 µs + Intl.Segmenter ┤■ 8.53 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.07x faster than Intl.Segmenter + 4.59x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 11.52x faster than @formatjs/intl-segmenter + 13.29x faster than graphemer + 26.24x faster than grapheme-splitter + +• Code snippet (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 9.99 µs/iter 10.05 µs ▃▃ █ + (9.67 µs … 10.19 µs) 10.16 µs ▆▁▁▁▁▁▁▁▁▄▄▆██▆█▆█▁▁▄ + gc(860.33 µs … 1.34 ms) 5.35 kb ( 5.33 kb… 5.35 kb) + +graphemer 116.51 µs/iter 117.46 µs █▇ + (108.63 µs … 153.75 µs) 142.08 µs ▁▂▆███▇▃▂▁▁▁▁▁▁▁▁▁▁▁▁ + gc(734.21 µs … 1.71 ms) 270.95 kb (270.40 kb…526.40 kb) + +grapheme-splitter 237.59 µs/iter 239.38 µs ▄█▂ + (208.29 µs … 272.04 µs) 264.08 µs ▁▁▁▁▁▁▁▂▄███▆▃▂▂▂▁▁▁▁ + gc(682.38 µs … 1.07 ms) 157.19 kb ( 22.39 kb…412.31 kb) + +@formatjs/intl-segmenter 149.59 µs/iter 150.92 µs ▄█ + (134.54 µs … 177.67 µs) 168.92 µs ▁▁▁▁▁▂▅███▆▃▃▂▂▂▁▁▁▁▁ + gc(740.83 µs … 1.79 ms) 236.20 kb (112.45 kb…288.82 kb) + +unicode-rs/unicode-segmentation (wasm-bindgen) 44.52 µs/iter 45.12 µs ██ + (43.43 µs … 45.62 µs) 45.55 µs █▁█▁▁█▁██▁█▁▁▁▁▁█▁▁██ + gc(900.75 µs … 1.71 ms) 2.74 kb ( 2.73 kb… 2.74 kb) + +Intl.Segmenter 18.97 µs/iter 18.96 µs █ █ █ + (18.82 µs … 19.26 µs) 19.19 µs ████▁███▁▁▁▁█▁▁▁▁▁▁██ + gc( 1.14 ms … 3.39 ms) 1.12 kb ( 1.11 kb… 1.12 kb) + + ┌ ┐ + unicode-segmenter/grapheme ┤ 9.99 µs + graphemer ┤■■■■■■■■■■■■■■■■ 116.51 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 237.59 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■ 149.59 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■ 44.52 µs + Intl.Segmenter ┤■ 18.97 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 1.9x faster than Intl.Segmenter + 4.45x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 11.66x faster than graphemer + 14.97x faster than @formatjs/intl-segmenter + 23.77x faster than grapheme-splitter + +! = run with sudo to enable hardware counters diff --git a/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-safari_26.5.2.txt b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-safari_26.5.2.txt new file mode 100644 index 0000000..4bea65e --- /dev/null +++ b/benchmark/grapheme/_records/20260727-apple_m4_pro-macos_26.5-safari_26.5.2.txt @@ -0,0 +1,197 @@ +clk: ~4.41 GHz +cpu: null +runtime: null (null) + +benchmark avg (min … max) p75 / p99 (min … top 1%) +------------------------------------------------------------- ------------------------------- +• Lorem ipsum (ascii) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 1.72 µs/iter 1.71 µs █ + (1.46 µs … 2.44 µs) 2.20 µs ▂▁▁▁▁▁▁█▁▁▁▁▁▂▁▁▁▁▁▁▁ +graphemer 18.27 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +grapheme-splitter 72.73 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂ +@formatjs/intl-segmenter 37.71 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +unicode-rs/unicode-segmentation (wasm-bindgen) 18.20 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +Intl.Segmenter 5.80 µs/iter 5.86 µs █ ▅ + (5.62 µs … 6.59 µs) 6.59 µs █▁▁▁▁█▁▁▁▁▁▁▁▁▁▃▁▁▁▁▂ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 1.72 µs + graphemer ┤■■■■■■■■ 18.27 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 72.73 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■ 37.71 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■ 18.20 µs + Intl.Segmenter ┤■■ 5.80 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 3.36x faster than Intl.Segmenter + 10.56x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 10.61x faster than graphemer + 21.89x faster than @formatjs/intl-segmenter + 42.21x faster than grapheme-splitter + +• Emojis +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 839.15 ns/iter 976.56 ns █ + (732.42 ns … 1.22 µs) 1.22 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▂ +graphemer 4.36 µs/iter 4.39 µs █ + (4.15 µs … 4.88 µs) 4.88 µs █▁▁▁▁▁▁█▁▁▁▁▁▃▁▁▁▁▁▁▁ +grapheme-splitter 16.60 µs/iter 16.60 µs █ + (16.36 µs … 17.33 µs) 17.09 µs █▁▁▁▁▁▁▇▁▁▁▁▁▃▁▁▁▁▁▁▅ +@formatjs/intl-segmenter 19.51 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +unicode-rs/unicode-segmentation (wasm-bindgen) 6.97 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 0.00 ps █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +Intl.Segmenter 4.67 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 0.00 ps █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 839.15 ns + graphemer ┤■■■■■■ 4.36 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 16.60 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 19.51 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■■■ 6.97 µs + Intl.Segmenter ┤■■■■■■■ 4.67 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 5.19x faster than graphemer + 5.57x faster than Intl.Segmenter + 8.3x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 19.78x faster than grapheme-splitter + 23.25x faster than @formatjs/intl-segmenter + +• Hindi +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 2.32 µs/iter 2.44 µs █ ▇ + (2.20 µs … 2.44 µs) 2.44 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +graphemer 15.87 µs/iter 15.63 µs █ + (15.38 µs … 18.07 µs) 17.82 µs █▁▆▁▁▁▃▁▁▁▃▁▁▁▁▁▁▁▁▁▃ +grapheme-splitter 32.37 µs/iter 32.71 µs █ ▄ + (31.74 µs … 33.45 µs) 33.20 µs █▁▁█▁▁▁▅▁▁▁▁▁█▁▁▁▁▁▁▅ +@formatjs/intl-segmenter 48.99 µs/iter 49.32 µs █ █ + (47.85 µs … 50.78 µs) 49.56 µs ▆▁▁▆▁▁▁▁▁█▁▆▁▁▆▁▁█▁▁▆ +unicode-rs/unicode-segmentation (wasm-bindgen) 13.81 µs/iter 13.92 µs █ ▄ + (13.67 µs … 14.16 µs) 14.16 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▃ +Intl.Segmenter 4.94 µs/iter 4.88 µs █ + (4.64 µs … 5.37 µs) 5.37 µs ▁▁▁▁▁▁▁█▁▁▁▁▁▅▁▁▁▁▁▁▁ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 2.32 µs + graphemer ┤■■■■■■■■■■ 15.87 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■ 32.37 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 48.99 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■ 13.81 µs + Intl.Segmenter ┤■■ 4.94 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.13x faster than Intl.Segmenter + 5.96x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 6.85x faster than graphemer + 13.97x faster than grapheme-splitter + 21.14x faster than @formatjs/intl-segmenter + +• Demonic characters +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 384.66 ns/iter 488.28 ns ▄ █ + (244.14 ns … 488.28 ns) 488.28 ns █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +graphemer 7.24 µs/iter 7.32 µs █ + (7.08 µs … 7.32 µs) 7.32 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ +grapheme-splitter 6.46 µs/iter 6.59 µs █ ▂ + (6.35 µs … 6.84 µs) 6.84 µs █▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▂ +@formatjs/intl-segmenter 60.14 µs/iter 60.79 µs ▃ █ ▃ + (59.08 µs … 61.52 µs) 61.28 µs █▁▁▁█▁▁▆▁▆▁▁▁▆▁▁█▁▁▁▆ +unicode-rs/unicode-segmentation (wasm-bindgen) 2.47 µs/iter 2.44 µs █ + (2.44 µs … 2.69 µs) 2.69 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ +Intl.Segmenter 2.64 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 0.00 ps █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 384.66 ns + graphemer ┤■■■■ 7.24 µs + grapheme-splitter ┤■■■ 6.46 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 60.14 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■ 2.47 µs + Intl.Segmenter ┤■ 2.64 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 6.42x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 6.86x faster than Intl.Segmenter + 16.78x faster than grapheme-splitter + 18.83x faster than graphemer + 156.35x faster than @formatjs/intl-segmenter + +• Tweet text (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 4.25 µs/iter 4.15 µs ▃ █ + (3.91 µs … 8.06 µs) 6.84 µs █▁█▃▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +graphemer 27.24 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +grapheme-splitter 53.77 µs/iter 55.66 µs █ + (52.00 µs … 58.11 µs) 56.88 µs █▅▁█▅▁▁▁▁▁▁▁▁▁▁▅▅▁▁▁▅ +@formatjs/intl-segmenter 55.99 µs/iter 56.40 µs █ █ █ █ + (55.18 µs … 57.62 µs) 56.88 µs █▁▁█▁▁█▁▁█▁▁▁▁█▁▁█▁▁█ +unicode-rs/unicode-segmentation (wasm-bindgen) 20.86 µs/iter 21.00 µs █ + (20.51 µs … 21.48 µs) 21.00 µs ▄▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ +Intl.Segmenter 11.17 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1000.00 µs █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 4.25 µs + graphemer ┤■■■■■■■■■■■■■■■ 27.24 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 53.77 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 55.99 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■■■ 20.86 µs + Intl.Segmenter ┤■■■■■ 11.17 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2.63x faster than Intl.Segmenter + 4.91x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 6.41x faster than graphemer + 12.66x faster than grapheme-splitter + 13.18x faster than @formatjs/intl-segmenter + +• Code snippet (combined) +------------------------------------------------------------- ------------------------------- +unicode-segmenter/grapheme 9.80 µs/iter 9.77 µs █ + (9.52 µs … 10.50 µs) 10.25 µs █▁▁▁▁▁▁█▁▁▁▁▁▃▁▁▁▁▁▁▄ +graphemer 54.70 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂ +grapheme-splitter 125.31 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ +@formatjs/intl-segmenter 124.37 µs/iter 0.00 ps █ + (0.00 ps … 1.00 ms) 1.00 ms █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ +unicode-rs/unicode-segmentation (wasm-bindgen) 50.35 µs/iter 50.05 µs ▃ █ ▃ + (48.58 µs … 57.86 µs) 51.51 µs █▁▆▁▁▁▁█▆▁█▁▆▁▁▁▁▁▁▁▆ +Intl.Segmenter 19.59 µs/iter 19.53 µs █ + (19.04 µs … 21.48 µs) 20.02 µs ▄▁▁▁▁█▁▁▁▁▆▁▁▁▁▄▁▁▁▁▄ + + ┌ ┐ + unicode-segmenter/grapheme ┤ 9.80 µs + graphemer ┤■■■■■■■■■■■■■ 54.70 µs + grapheme-splitter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 125.31 µs + @formatjs/intl-segmenter ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 124.37 µs +unicode-rs/unicode-segmentation (wasm-bindgen) ┤■■■■■■■■■■■■ 50.35 µs + Intl.Segmenter ┤■■■ 19.59 µs + └ ┘ + +summary + unicode-segmenter/grapheme + 2x faster than Intl.Segmenter + 5.14x faster than unicode-rs/unicode-segmentation (wasm-bindgen) + 5.58x faster than graphemer + 12.69x faster than @formatjs/intl-segmenter + 12.79x faster than grapheme-splitter diff --git a/benchmark/grapheme/_records/report.generated.html b/benchmark/grapheme/_records/report.generated.html index d621022..fd5eea4 100644 --- a/benchmark/grapheme/_records/report.generated.html +++ b/benchmark/grapheme/_records/report.generated.html @@ -162,7 +162,7 @@
Grapheme segmentation, six workloads × six implementations. Longer bars are better (throughput). Generated 2026-07-07 from the records in this directory.
+Grapheme segmentation, six workloads × six implementations. Longer bars are better (throughput). Generated 2026-07-27 from the records in this directory.