Skip to content

Commit 7d7319f

Browse files
authored
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.
1 parent f9f4cac commit 7d7319f

File tree

5 files changed

+52
-26
lines changed

5 files changed

+52
-26
lines changed

Diff for: .changeset/three-nails-repair.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: minor
3+
swc_ecma_codegen: minor
4+
---
5+
6+
perf(es/codegen): Reduce allocation using `compact_str`

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crates/swc_ecma_codegen/Cargo.toml

+10-9
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ serde-impl = ["swc_ecma_ast/serde"]
1717
bench = false
1818

1919
[dependencies]
20-
ascii = { workspace = true }
21-
memchr = { workspace = true }
22-
num-bigint = { workspace = true, features = ["serde"] }
23-
once_cell = { workspace = true }
24-
regex = { workspace = true }
25-
rustc-hash = { workspace = true }
26-
serde = { workspace = true }
27-
sourcemap = { workspace = true }
28-
tracing = { workspace = true }
20+
ascii = { workspace = true }
21+
compact_str = { workspace = true }
22+
memchr = { workspace = true }
23+
num-bigint = { workspace = true, features = ["serde"] }
24+
once_cell = { workspace = true }
25+
regex = { workspace = true }
26+
rustc-hash = { workspace = true }
27+
serde = { workspace = true }
28+
sourcemap = { workspace = true }
29+
tracing = { workspace = true }
2930

3031
swc_allocator = { version = "3.0.0", path = "../swc_allocator", default-features = false }
3132
swc_atoms = { version = "3.1.0", path = "../swc_atoms" }

Diff for: crates/swc_ecma_codegen/src/lib.rs

+34-16
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
#![allow(clippy::nonminimal_bool)]
66
#![allow(non_local_definitions)]
77

8-
use std::{borrow::Cow, fmt::Write, io, str};
8+
use std::{borrow::Cow, fmt::Write, io, ops::Deref, str};
99

1010
use ascii::AsciiChar;
11+
use compact_str::{format_compact, CompactString};
1112
use memchr::memmem::Finder;
1213
use once_cell::sync::Lazy;
1314
use swc_allocator::maybe::vec::Vec;
@@ -108,7 +109,23 @@ where
108109
pub wr: W,
109110
}
110111

111-
fn replace_close_inline_script(raw: &str) -> Cow<str> {
112+
enum CowStr<'a> {
113+
Borrowed(&'a str),
114+
Owned(CompactString),
115+
}
116+
117+
impl Deref for CowStr<'_> {
118+
type Target = str;
119+
120+
fn deref(&self) -> &str {
121+
match self {
122+
CowStr::Borrowed(s) => s,
123+
CowStr::Owned(s) => s.as_str(),
124+
}
125+
}
126+
}
127+
128+
fn replace_close_inline_script(raw: &str) -> CowStr {
112129
let chars = raw.as_bytes();
113130
let pattern_len = 8; // </script>
114131

@@ -128,16 +145,16 @@ fn replace_close_inline_script(raw: &str) -> Cow<str> {
128145
.peekable();
129146

130147
if matched_indexes.peek().is_none() {
131-
return Cow::Borrowed(raw);
148+
return CowStr::Borrowed(raw);
132149
}
133150

134-
let mut result = String::from(raw);
151+
let mut result = CompactString::new(raw);
135152

136153
for (offset, i) in matched_indexes.enumerate() {
137154
result.insert(i + 1 + offset, '\\');
138155
}
139156

140-
Cow::Owned(result)
157+
CowStr::Owned(result)
141158
}
142159

143160
static NEW_LINE_TPL_REGEX: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"\\n|\n").unwrap());
@@ -684,10 +701,11 @@ where
684701
let (quote_char, mut value) = get_quoted_utf16(&node.value, self.cfg.ascii_only, target);
685702

686703
if self.cfg.inline_script {
687-
value = Cow::Owned(
704+
value = CowStr::Owned(
688705
replace_close_inline_script(&value)
689706
.replace("\x3c!--", "\\x3c!--")
690-
.replace("--\x3e", "--\\x3e"),
707+
.replace("--\x3e", "--\\x3e")
708+
.into(),
691709
);
692710
}
693711

@@ -3965,13 +3983,13 @@ fn get_template_element_from_raw(s: &str, ascii_only: bool) -> String {
39653983
buf
39663984
}
39673985

3968-
fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> Cow<str> {
3986+
fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> CowStr {
39693987
if sym.is_ascii() {
3970-
return Cow::Borrowed(sym);
3988+
return CowStr::Borrowed(sym);
39713989
}
39723990

39733991
let mut first = true;
3974-
let mut buf = String::with_capacity(sym.len() + 8);
3992+
let mut buf = CompactString::with_capacity(sym.len() + 8);
39753993
let mut iter = sym.chars().peekable();
39763994
let mut need_quote = false;
39773995

@@ -4133,14 +4151,14 @@ fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> C
41334151
}
41344152

41354153
if need_quote {
4136-
Cow::Owned(format!("\"{}\"", buf))
4154+
CowStr::Owned(format_compact!("\"{}\"", buf))
41374155
} else {
4138-
Cow::Owned(buf)
4156+
CowStr::Owned(buf)
41394157
}
41404158
}
41414159

41424160
/// Returns `(quote_char, value)`
4143-
fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, Cow<str>) {
4161+
fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) {
41444162
// Fast path: If the string is ASCII and doesn't need escaping, we can avoid
41454163
// allocation
41464164
if v.is_ascii() {
@@ -4172,7 +4190,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,
41724190
if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0)
41734191
|| (quote_char == AsciiChar::Quotation && double_quote_count == 0)
41744192
{
4175-
return (quote_char, Cow::Borrowed(v));
4193+
return (quote_char, CowStr::Borrowed(v));
41764194
}
41774195
}
41784196
}
@@ -4207,7 +4225,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,
42074225

42084226
// Add 1 for each escaped quote
42094227
let capacity = v.len() + escape_count;
4210-
let mut buf = String::with_capacity(capacity);
4228+
let mut buf = CompactString::with_capacity(capacity);
42114229

42124230
let mut iter = v.chars().peekable();
42134231
while let Some(c) = iter.next() {
@@ -4354,7 +4372,7 @@ fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar,
43544372
}
43554373
}
43564374

4357-
(quote_char, Cow::Owned(buf))
4375+
(quote_char, CowStr::Owned(buf))
43584376
}
43594377

43604378
fn handle_invalid_unicodes(s: &str) -> Cow<str> {

Diff for: crates/swc_ecma_codegen/src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ CONTENT\r
584584

585585
#[test]
586586
fn test_get_quoted_utf16() {
587-
fn combine((quote_char, s): (AsciiChar, Cow<str>)) -> String {
587+
fn combine((quote_char, s): (AsciiChar, CowStr<'_>)) -> String {
588588
let mut new = String::with_capacity(s.len() + 2);
589589
new.push(quote_char.as_char());
590590
new.push_str(s.as_ref());

0 commit comments

Comments
 (0)