Skip to content

Commit 4450f2f

Browse files
committed
Perf option: ascii happy path
1 parent 76c63a5 commit 4450f2f

File tree

4 files changed

+160
-13
lines changed

4 files changed

+160
-13
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ features = [
4747
"fast-rng" # Use a faster (but still sufficiently random) RNG
4848
]
4949

50+
[[bench]]
51+
name = "parse_bench"
52+
harness = false
53+
5054
[dev-dependencies]
5155
difference = "2"
5256
regex = "1.11.1"

benches/parse_bench.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use content_tag::{Options, Preprocessor};
2+
use std::time::Instant;
3+
4+
fn bench_parse(name: &str, src: &str, iterations: u32) -> f64 {
5+
// Warmup
6+
for _ in 0..100 {
7+
let p = Preprocessor::new();
8+
let _ = p.parse(src, Options::default());
9+
}
10+
11+
// Run 3 rounds, take the minimum
12+
let mut best = f64::MAX;
13+
for _ in 0..3 {
14+
let start = Instant::now();
15+
for _ in 0..iterations {
16+
let p = Preprocessor::new();
17+
let _ = p.parse(src, Options::default());
18+
}
19+
let elapsed = start.elapsed();
20+
let per_iter = elapsed.as_nanos() as f64 / iterations as f64;
21+
if per_iter < best {
22+
best = per_iter;
23+
}
24+
}
25+
26+
println!(
27+
"{:<35} {:>8.1}µs per parse ({} chars)",
28+
name,
29+
best / 1000.0,
30+
src.len(),
31+
);
32+
best / 1000.0
33+
}
34+
35+
fn main() {
36+
println!("=== Fine-grained scaling: 1 to 20 templates (ASCII) ===\n");
37+
38+
let chunk = r#"
39+
import Component from '@glimmer/component';
40+
class Comp extends Component {
41+
<template>
42+
<div class="container">
43+
<h1>{{this.title}}</h1>
44+
<p>{{this.description}}</p>
45+
</div>
46+
</template>
47+
}
48+
"#;
49+
50+
let mut results = Vec::new();
51+
for repeats in 1..=20 {
52+
let src = chunk.repeat(repeats);
53+
let us = bench_parse(
54+
&format!("{:>2} templates ({:>4} chars)", repeats, src.len()),
55+
&src,
56+
3000,
57+
);
58+
results.push((repeats, src.len(), us));
59+
}
60+
61+
println!("\n=== Per-template marginal cost ===\n");
62+
for i in 1..results.len() {
63+
let (t, _, us) = results[i];
64+
let (_, _, prev_us) = results[i - 1];
65+
let marginal = us - prev_us;
66+
println!(
67+
"template {:>2}: +{:>5.1}µs marginal cost ({:.1}µs total, {:.1}µs/template avg)",
68+
t,
69+
marginal,
70+
us,
71+
us / t as f64
72+
);
73+
}
74+
75+
println!("\n=== Non-ASCII scaling: 1 to 10 templates ===\n");
76+
77+
let mb_chunk = "
78+
import Component from '@glimmer/component';
79+
class Comp extends Component {
80+
<template>
81+
<div class=\"container\">
82+
<h1>{{this.title}} 🎉 中文</h1>
83+
<p>{{this.description}}</p>
84+
</div>
85+
</template>
86+
}
87+
";
88+
89+
for repeats in [1, 2, 3, 5, 10] {
90+
let src = mb_chunk.repeat(repeats);
91+
bench_parse(
92+
&format!(
93+
"{:>2} templates, multibyte ({:>4} chars)",
94+
repeats,
95+
src.len()
96+
),
97+
&src,
98+
3000,
99+
);
100+
}
101+
102+
println!("\n=== Typical .gts files ===\n");
103+
104+
let small = r#"
105+
import Component from '@glimmer/component';
106+
export default class extends Component {
107+
<template><div>{{this.title}}</div></template>
108+
}"#;
109+
110+
let no_template = r#"
111+
import { tracked } from '@glimmer/tracking';
112+
import { action } from '@ember/object';
113+
import Service, { service } from '@ember/service';
114+
115+
export default class AuthService extends Service {
116+
@service declare session: any;
117+
@tracked count = 0;
118+
119+
@action
120+
increment() { this.count++; }
121+
122+
get doubled() { return this.count * 2; }
123+
}"#;
124+
125+
bench_parse("small component (1 template)", small, 5000);
126+
bench_parse("utility file (no template)", no_template, 5000);
127+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ impl Preprocessor {
8888

8989
let mut visitor = locate::LocateContentTagVisitor {
9090
occurrences: Default::default(),
91+
is_ascii: src.is_ascii(),
9192
src: src.to_string(),
9293
};
9394

src/locate.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use swc_ecma_ast::{
66
};
77
use swc_ecma_visit::{Visit, VisitWith};
88

9-
#[derive(Default, Debug)]
9+
#[derive(Debug)]
1010
pub struct LocateContentTagVisitor {
1111
pub occurrences: Vec<Occurrence>,
1212
pub src: String,
13+
pub is_ascii: bool,
1314
}
1415

1516
#[derive(Eq, PartialEq, Debug, Serialize)]
@@ -32,10 +33,10 @@ impl LocateContentTagVisitor {
3233
kind,
3334
tag_name: "template".to_owned(),
3435
contents: contents.value.to_string(),
35-
range: Range::new(&self.src, span),
36-
start_range: Range::new(&self.src, &opening.span),
37-
content_range: Range::new(&self.src, &contents.span),
38-
end_range: Range::new(&self.src, &closing.span),
36+
range: Range::new(&self.src, span, self.is_ascii),
37+
start_range: Range::new(&self.src, &opening.span, self.is_ascii),
38+
content_range: Range::new(&self.src, &contents.span, self.is_ascii),
39+
end_range: Range::new(&self.src, &closing.span, self.is_ascii),
3940
};
4041

4142
self.occurrences.push(occurrence);
@@ -108,14 +109,28 @@ pub struct Range {
108109
end_utf16_codepoint: usize,
109110
}
110111
impl Range {
111-
pub fn new(src: &str, span: &Span) -> Range {
112-
Range {
113-
start_byte: span.lo.0 as usize - 1,
114-
end_byte: span.hi.0 as usize - 1,
115-
start_char: src[..span.lo.0 as usize - 1].chars().count(),
116-
end_char: src[..span.hi.0 as usize - 1].chars().count(),
117-
start_utf16_codepoint: src[..span.lo.0 as usize - 1].encode_utf16().count(),
118-
end_utf16_codepoint: src[..span.hi.0 as usize - 1].encode_utf16().count(),
112+
pub fn new(src: &str, span: &Span, is_ascii: bool) -> Range {
113+
let start_byte = span.lo.0 as usize - 1;
114+
let end_byte = span.hi.0 as usize - 1;
115+
if is_ascii {
116+
// For ASCII sources, byte/char/utf16 offsets are all identical.
117+
Range {
118+
start_byte,
119+
end_byte,
120+
start_char: start_byte,
121+
end_char: end_byte,
122+
start_utf16_codepoint: start_byte,
123+
end_utf16_codepoint: end_byte,
124+
}
125+
} else {
126+
Range {
127+
start_byte,
128+
end_byte,
129+
start_char: src[..start_byte].chars().count(),
130+
end_char: src[..end_byte].chars().count(),
131+
start_utf16_codepoint: src[..start_byte].encode_utf16().count(),
132+
end_utf16_codepoint: src[..end_byte].encode_utf16().count(),
133+
}
119134
}
120135
}
121136
}

0 commit comments

Comments
 (0)