From 7d7319f248afe10f33da2a7201c1a90ec58a441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Mon, 10 Feb 2025 01:55:31 +0900 Subject: [PATCH] perf(es/codegen): Reduce allocation using `compact_str` (#10008) **Description:** Previously, we allocated if there's an escape character. With this PR, we allocate if there's an escape character **and** it's long enough. --- .changeset/three-nails-repair.md | 6 ++++ Cargo.lock | 1 + crates/swc_ecma_codegen/Cargo.toml | 19 ++++++----- crates/swc_ecma_codegen/src/lib.rs | 50 +++++++++++++++++++--------- crates/swc_ecma_codegen/src/tests.rs | 2 +- 5 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 .changeset/three-nails-repair.md diff --git a/.changeset/three-nails-repair.md b/.changeset/three-nails-repair.md new file mode 100644 index 000000000000..809a10d0ed9d --- /dev/null +++ b/.changeset/three-nails-repair.md @@ -0,0 +1,6 @@ +--- +swc_core: minor +swc_ecma_codegen: minor +--- + +perf(es/codegen): Reduce allocation using `compact_str` diff --git a/Cargo.lock b/Cargo.lock index d48383e61364..4109a1cef267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5048,6 +5048,7 @@ dependencies = [ "ascii", "base64 0.21.7", "codspeed-criterion-compat", + "compact_str", "criterion", "memchr", "num-bigint", diff --git a/crates/swc_ecma_codegen/Cargo.toml b/crates/swc_ecma_codegen/Cargo.toml index 9c7db4a18473..0a1790f05be1 100644 --- a/crates/swc_ecma_codegen/Cargo.toml +++ b/crates/swc_ecma_codegen/Cargo.toml @@ -17,15 +17,16 @@ serde-impl = ["swc_ecma_ast/serde"] bench = false [dependencies] -ascii = { workspace = true } -memchr = { workspace = true } -num-bigint = { workspace = true, features = ["serde"] } -once_cell = { workspace = true } -regex = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true } -sourcemap = { workspace = true } -tracing = { workspace = true } +ascii = { workspace = true } +compact_str = { workspace = true } +memchr = { workspace = true } +num-bigint = { workspace = true, features = ["serde"] } +once_cell = { workspace = true } +regex = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true } +sourcemap = { workspace = true } +tracing = { workspace = true } swc_allocator = { version = "3.0.0", path = "../swc_allocator", default-features = false } swc_atoms = { version = "3.1.0", path = "../swc_atoms" } diff --git a/crates/swc_ecma_codegen/src/lib.rs b/crates/swc_ecma_codegen/src/lib.rs index f311f0725f5f..19f9b611e1fd 100644 --- a/crates/swc_ecma_codegen/src/lib.rs +++ b/crates/swc_ecma_codegen/src/lib.rs @@ -5,9 +5,10 @@ #![allow(clippy::nonminimal_bool)] #![allow(non_local_definitions)] -use std::{borrow::Cow, fmt::Write, io, str}; +use std::{borrow::Cow, fmt::Write, io, ops::Deref, str}; use ascii::AsciiChar; +use compact_str::{format_compact, CompactString}; use memchr::memmem::Finder; use once_cell::sync::Lazy; use swc_allocator::maybe::vec::Vec; @@ -108,7 +109,23 @@ where pub wr: W, } -fn replace_close_inline_script(raw: &str) -> Cow { +enum CowStr<'a> { + Borrowed(&'a str), + Owned(CompactString), +} + +impl Deref for CowStr<'_> { + type Target = str; + + fn deref(&self) -> &str { + match self { + CowStr::Borrowed(s) => s, + CowStr::Owned(s) => s.as_str(), + } + } +} + +fn replace_close_inline_script(raw: &str) -> CowStr { let chars = raw.as_bytes(); let pattern_len = 8; // @@ -128,16 +145,16 @@ fn replace_close_inline_script(raw: &str) -> Cow { .peekable(); if matched_indexes.peek().is_none() { - return Cow::Borrowed(raw); + return CowStr::Borrowed(raw); } - let mut result = String::from(raw); + let mut result = CompactString::new(raw); for (offset, i) in matched_indexes.enumerate() { result.insert(i + 1 + offset, '\\'); } - Cow::Owned(result) + CowStr::Owned(result) } static NEW_LINE_TPL_REGEX: Lazy = Lazy::new(|| regex::Regex::new(r"\\n|\n").unwrap()); @@ -684,10 +701,11 @@ where let (quote_char, mut value) = get_quoted_utf16(&node.value, self.cfg.ascii_only, target); if self.cfg.inline_script { - value = Cow::Owned( + value = CowStr::Owned( replace_close_inline_script(&value) .replace("\x3c!--", "\\x3c!--") - .replace("--\x3e", "--\\x3e"), + .replace("--\x3e", "--\\x3e") + .into(), ); } @@ -3965,13 +3983,13 @@ fn get_template_element_from_raw(s: &str, ascii_only: bool) -> String { buf } -fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> Cow { +fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> CowStr { if sym.is_ascii() { - return Cow::Borrowed(sym); + return CowStr::Borrowed(sym); } let mut first = true; - let mut buf = String::with_capacity(sym.len() + 8); + let mut buf = CompactString::with_capacity(sym.len() + 8); let mut iter = sym.chars().peekable(); let mut need_quote = false; @@ -4133,14 +4151,14 @@ fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> C } if need_quote { - Cow::Owned(format!("\"{}\"", buf)) + CowStr::Owned(format_compact!("\"{}\"", buf)) } else { - Cow::Owned(buf) + CowStr::Owned(buf) } } /// Returns `(quote_char, value)` -fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, Cow) { +fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) { // Fast path: If the string is ASCII and doesn't need escaping, we can avoid // allocation if v.is_ascii() { @@ -4172,7 +4190,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0) || (quote_char == AsciiChar::Quotation && double_quote_count == 0) { - return (quote_char, Cow::Borrowed(v)); + return (quote_char, CowStr::Borrowed(v)); } } } @@ -4207,7 +4225,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, // Add 1 for each escaped quote let capacity = v.len() + escape_count; - let mut buf = String::with_capacity(capacity); + let mut buf = CompactString::with_capacity(capacity); let mut iter = v.chars().peekable(); while let Some(c) = iter.next() { @@ -4354,7 +4372,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, } } - (quote_char, Cow::Owned(buf)) + (quote_char, CowStr::Owned(buf)) } fn handle_invalid_unicodes(s: &str) -> Cow { diff --git a/crates/swc_ecma_codegen/src/tests.rs b/crates/swc_ecma_codegen/src/tests.rs index b31ebd0fc01c..0c8086aa71df 100644 --- a/crates/swc_ecma_codegen/src/tests.rs +++ b/crates/swc_ecma_codegen/src/tests.rs @@ -584,7 +584,7 @@ CONTENT\r #[test] fn test_get_quoted_utf16() { - fn combine((quote_char, s): (AsciiChar, Cow)) -> String { + fn combine((quote_char, s): (AsciiChar, CowStr<'_>)) -> String { let mut new = String::with_capacity(s.len() + 2); new.push(quote_char.as_char()); new.push_str(s.as_ref());