From d44462a70fe5276d032f8ffee3e1d9bf9fa35aec Mon Sep 17 00:00:00 2001 From: Boshen Date: Fri, 31 Jan 2025 16:21:50 +0800 Subject: [PATCH] perf(codegen): avoid a heap allocation when printing floats --- Cargo.lock | 1 + crates/oxc_codegen/Cargo.toml | 3 ++- crates/oxc_codegen/src/lib.rs | 31 ++++++++++++++++--------------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8ccdda96a7ea..8696678dfd662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1692,6 +1692,7 @@ dependencies = [ "oxc_syntax", "pico-args", "rustc-hash", + "ryu-js", ] [[package]] diff --git a/crates/oxc_codegen/Cargo.toml b/crates/oxc_codegen/Cargo.toml index f8207d39635ad..0575d7b6c9271 100644 --- a/crates/oxc_codegen/Cargo.toml +++ b/crates/oxc_codegen/Cargo.toml @@ -27,13 +27,14 @@ oxc_index = { workspace = true } oxc_mangler = { workspace = true } oxc_sourcemap = { workspace = true } oxc_span = { workspace = true } -oxc_syntax = { workspace = true, features = ["to_js_string"] } +oxc_syntax = { workspace = true } assert-unchecked = { workspace = true } bitflags = { workspace = true } cow-utils = { workspace = true } nonmax = { workspace = true } rustc-hash = { workspace = true } +ryu-js = { workspace = true } [dev-dependencies] base64 = { workspace = true } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 3e672779445b2..cdf157e0173bb 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -575,12 +575,13 @@ impl<'a> Codegen<'a> { } fn print_non_negative_float(&mut self, num: f64) { - use oxc_syntax::number::ToJsString; + // Inline the buffer here to avoid heap allocation on `buffer.format(*self).to_string()`. + let mut buffer = ryu_js::Buffer::new(); if num < 1000.0 && num.fract() == 0.0 { - self.print_str(&num.to_js_string()); + self.print_str(buffer.format(num)); self.need_space_before_dot = self.code_len(); } else { - let s = Self::get_minified_number(num); + let s = Self::get_minified_number(num, &mut buffer); self.print_str(&s); if !s.bytes().any(|b| matches!(b, b'.' | b'e' | b'x')) { self.need_space_before_dot = self.code_len(); @@ -691,25 +692,25 @@ impl<'a> Codegen<'a> { // `get_minified_number` from terser // https://github.com/terser/terser/blob/c5315c3fd6321d6b2e076af35a70ef532f498505/lib/output.js#L2418 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)] - fn get_minified_number(num: f64) -> String { + fn get_minified_number(num: f64, buffer: &mut ryu_js::Buffer) -> Cow<'_, str> { use cow_utils::CowUtils; - use oxc_syntax::number::ToJsString; + if num < 1000.0 && num.fract() == 0.0 { - return num.to_js_string(); + return Cow::Borrowed(buffer.format(num)); } - let mut s = num.to_js_string(); + let mut s = buffer.format(num); if s.starts_with("0.") { - s = s[1..].to_string(); + s = &s[1..]; } - s = s.cow_replacen("e+", "e", 1).to_string(); + let s = s.cow_replacen("e+", "e", 1); let mut candidates = vec![s.clone()]; if num.fract() == 0.0 { - candidates.push(format!("0x{:x}", num as u128)); + candidates.push(Cow::Owned(format!("0x{:x}", num as u128))); } // create `1e-2` @@ -717,14 +718,14 @@ impl<'a> Codegen<'a> { if let Some((i, _)) = s[1..].bytes().enumerate().find(|(_, c)| *c != b'0') { let len = i + 1; // `+1` to include the dot. let digits = &s[len..]; - candidates.push(format!("{digits}e-{}", digits.len() + len - 1)); + candidates.push(Cow::Owned(format!("{digits}e-{}", digits.len() + len - 1))); } } // create 1e2 if s.ends_with('0') { if let Some((len, _)) = s.bytes().rev().enumerate().find(|(_, c)| *c != b'0') { - candidates.push(format!("{}e{len}", &s[0..s.len() - len])); + candidates.push(Cow::Owned(format!("{}e{len}", &s[0..s.len() - len]))); } } @@ -733,13 +734,13 @@ impl<'a> Codegen<'a> { if let Some((integer, point, exponent)) = s.split_once('.').and_then(|(a, b)| b.split_once('e').map(|e| (a, e.0, e.1))) { - candidates.push(format!( + candidates.push(Cow::Owned(format!( "{integer}{point}e{}", exponent.parse::().unwrap() - point.len() as isize - )); + ))); } - candidates.into_iter().min_by_key(String::len).unwrap() + candidates.into_iter().min_by_key(|c| c.len()).unwrap() } fn add_source_mapping(&mut self, span: Span) {