perf(parser): replace dragonbox_ecma with zmij_ecma (#28282)

Use `zmij_ecma` to reduce float-formatting time while preserving
ECMAScript output. Local Apple M4 Pro measurements showed 21–66% less
conversion time and about 18% less time generating synthetic
numeric-heavy JavaScript; real-world bundles were roughly unchanged.

AI-assisted with Codex.
This commit is contained in:
Boshen authored and GitHub committed 2026-10-28 21:34:45 +08:00
1 parent 0bae420d3c
commit 8239daca6e
10 files changed
+24 -23

No files matched your search

Generated
+10 -10
View File
@@ -592,12 +592,6 @@ dependencies = [
"litrs",
]
[[package]]
name = "dragonbox_ecma"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf"
[[package]]
name = "dyn-clone"
version = "1.0.20"
@@ -2012,7 +2006,6 @@ name = "oxc_ecmascript"
version = "0.156.0"
dependencies = [
"cow-utils",
"dragonbox_ecma",
"itoa",
"memchr",
"num-bigint",
@@ -2026,15 +2019,16 @@ dependencies = [
"oxc_str",
"oxc_syntax",
"smallvec",
"zmij_ecma",
]
[[package]]
name = "oxc_estree"
version = "0.156.0"
dependencies = [
"dragonbox_ecma",
"itoa",
"oxc_data_structures",
"zmij_ecma",
]
[[package]]
@@ -2133,7 +2127,6 @@ dependencies = [
name = "oxc_formatter_json"
version = "0.75.0"
dependencies = [
"dragonbox_ecma",
"insta",
"oxc_allocator",
"oxc_ast",
@@ -2146,6 +2139,7 @@ dependencies = [
"oxc_syntax",
"oxc_tasks_common",
"pico-args",
"zmij_ecma",
]
[[package]]
@@ -2691,7 +2685,6 @@ version = "0.156.0"
dependencies = [
"bitflags",
"cow-utils",
"dragonbox_ecma",
"nonmax",
"oxc_allocator",
"oxc_ast_macros",
@@ -2702,6 +2695,7 @@ dependencies = [
"phf",
"serde",
"unicode-id-start",
"zmij_ecma",
]
[[package]]
@@ -4667,3 +4661,9 @@ name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zmij_ecma"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458b3e7a977f14499066b6bdb63d69da30ce337d6c160f077e6c575c82a44db9"
+1 -1
View File
@@ -215,7 +215,6 @@ constcat = "0.6.1" # Const string concatenation
convert_case = "0.12.0" # Case conversion
cow-utils = "0.1.3" # Copy-on-write utilities
criterion2 = { version = "3.0.4", default-features = false } # Benchmarking
dragonbox_ecma = "0.1.12" # Fast float formatting
editorconfig-parser = "0.0.4" # EditorConfig parsing
encoding_rs = "0.8.35" # Character encoding
encoding_rs_io = "0.1.7" # Encoding I/O
@@ -281,6 +280,7 @@ ureq = { version = "3.3.0", default-features = false } # HTTP client
url = { version = "2.5.8" } # URL parsing
walkdir = "2.5.0" # Directory traversal
windows-sys = { version = "0.61.2" } # Windows API bindings
zmij_ecma = "1.0.23" # Fast float formatting
[workspace.metadata.cargo-shear]
ignored = [
+1 -1
View File
@@ -35,12 +35,12 @@ oxc_str = { workspace = true }
oxc_syntax = { workspace = true, features = ["to_js_string"] }
cow-utils = { workspace = true, optional = true }
dragonbox_ecma = { workspace = true }
itoa = { workspace = true }
memchr = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
smallvec = { workspace = true, optional = true }
zmij_ecma = { workspace = true }
[dev-dependencies]
oxc_allocator = { workspace = true }
+1 -1
View File
@@ -18,7 +18,7 @@ pub fn with_number_literal<R>(value: f64, f: impl FnOnce(&str) -> R) -> R {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
fn number_literal(value: f64) -> LiteralString {
let mut buffer = dragonbox_ecma::Buffer::new();
let mut buffer = zmij_ecma::Buffer::new();
let formatted = buffer.format(value);
let mut best_candidate = LiteralString::new();
for (index, byte) in formatted.bytes().enumerate() {
+2 -2
View File
@@ -22,12 +22,12 @@ oxc_data_structures = { workspace = true, features = [
"stack",
], optional = true }
dragonbox_ecma = { workspace = true, optional = true }
itoa = { workspace = true, optional = true }
zmij_ecma = { workspace = true, optional = true }
[features]
default = []
serialize = ["dep:oxc_data_structures", "dep:itoa", "dep:dragonbox_ecma"]
serialize = ["dep:oxc_data_structures", "dep:itoa", "dep:zmij_ecma"]
[lib]
doctest = false
@@ -1,5 +1,5 @@
use dragonbox_ecma::Buffer as DragonboxBuffer;
use itoa::Buffer as ItoaBuffer;
use zmij_ecma::Buffer as ZmijBuffer;
use super::{ESTree, Serializer};
@@ -14,7 +14,7 @@ impl ESTree for bool {
impl ESTree for f64 {
fn serialize<S: Serializer>(&self, mut serializer: S) {
if self.is_finite() {
let mut buffer = DragonboxBuffer::new();
let mut buffer = ZmijBuffer::new();
let s = buffer.format_finite(*self);
serializer.buffer_mut().print_str(s);
} else if self.is_nan() {
+2 -1
View File
@@ -17,7 +17,6 @@ description.workspace = true
workspace = true
[dependencies]
dragonbox_ecma = { workspace = true }
oxc_allocator = { workspace = true }
oxc_ast = { workspace = true }
oxc_diagnostics = { workspace = true }
@@ -27,6 +26,8 @@ oxc_span = { workspace = true }
oxc_str = { workspace = true }
oxc_syntax = { workspace = true }
zmij_ecma = { workspace = true }
[dev-dependencies]
insta = { workspace = true }
oxc_formatter_tests = { workspace = true, features = ["conformance"] }
+2 -2
View File
@@ -44,12 +44,12 @@ pub fn write_quoted_str<'a>(f: &mut JsonFormatter<'_, 'a>, quote_byte: u8, body:
/// JS stringifies the parsed value into a different shape (`"16"` for `0x10`, `"NaN"` for `1_2`),
/// Rust simply fails to parse, so `Err` mapping to `false` matches.
pub fn number_string_round_trips(s: &str) -> bool {
// PERF: Use `dragonbox_ecma` directly instead of `oxc_syntax::ToJsString`
// PERF: Use `zmij_ecma` directly instead of `oxc_syntax::ToJsString`
// The latter returns `String` (heap alloc),
// while `Buffer::format` returns `&str` borrowed from a stack buffer.
// This is only a `==` comparison on a hot path (every numeric key), so avoid the alloc.
s.parse::<f64>()
.is_ok_and(|value| value.is_finite() && dragonbox_ecma::Buffer::new().format(value) == s)
.is_ok_and(|value| value.is_finite() && zmij_ecma::Buffer::new().format(value) == s)
}
/// Wraps a re-entrant JSON closure in a [`FormatWith`]. The closure's context is
+2 -2
View File
@@ -33,10 +33,10 @@ nonmax = { workspace = true }
phf = { workspace = true, features = ["macros"] }
unicode-id-start = { workspace = true }
dragonbox_ecma = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
zmij_ecma = { workspace = true, optional = true }
[features]
default = []
to_js_string = ["dep:dragonbox_ecma"]
to_js_string = ["dep:zmij_ecma"]
serialize = ["bitflags/serde", "dep:serde", "oxc_estree/serialize"]
+1 -1
View File
@@ -44,7 +44,7 @@ pub trait ToJsString {
#[cfg(feature = "to_js_string")]
impl ToJsString for f64 {
fn to_js_string(&self) -> String {
let mut buffer = dragonbox_ecma::Buffer::new();
let mut buffer = zmij_ecma::Buffer::new();
buffer.format(*self).to_string()
}
}