mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
1 parent
0bae420d3c
commit
8239daca6e
10 files changed
+24
-23
No files matched your search
Generated
+10
-10
@@ -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
@@ -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 = [
|
||||
|
||||
@@ -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 }
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user