Skip to content

Commit f15f007

Browse files
authored
fix: perf bug for large replace source (#156)
1 parent 09aa874 commit f15f007

File tree

9 files changed

+36966
-98
lines changed

9 files changed

+36966
-98
lines changed

benches/bench.rs

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![allow(missing_docs)]
22

3+
mod bench_complex_replace_source;
4+
35
use std::collections::HashMap;
46

57
#[cfg(not(codspeed))]
@@ -9,10 +11,12 @@ pub use criterion::*;
911
pub use codspeed_criterion_compat::*;
1012

1113
use rspack_sources::{
12-
BoxSource, CachedSource, ConcatSource, MapOptions, ReplaceSource, Source,
13-
SourceExt, SourceMap, SourceMapSource, SourceMapSourceOptions,
14+
BoxSource, CachedSource, ConcatSource, MapOptions, Source, SourceExt,
15+
SourceMap, SourceMapSource, SourceMapSourceOptions,
1416
};
1517

18+
use bench_complex_replace_source::benchmark_complex_replace_source;
19+
1620
const HELLOWORLD_JS: &str = include_str!(concat!(
1721
env!("CARGO_MANIFEST_DIR"),
1822
"/benches/fixtures/transpile-minify/files/helloworld.js"
@@ -164,39 +168,6 @@ fn benchmark_concat_generate_base64_with_cache(b: &mut Bencher) {
164168
})
165169
}
166170

167-
fn benchmark_replace_large_minified_source(b: &mut Bencher) {
168-
let antd_minify = SourceMapSource::new(SourceMapSourceOptions {
169-
value: ANTD_MIN_JS,
170-
name: "antd.min.js",
171-
source_map: SourceMap::from_json(ANTD_MIN_JS_MAP).unwrap(),
172-
original_source: None,
173-
inner_source_map: None,
174-
remove_original_source: false,
175-
});
176-
let mut replace_source = ReplaceSource::new(antd_minify);
177-
replace_source.replace(107, 114, "exports", None);
178-
replace_source.replace(130, 143, "'object'", None);
179-
replace_source.replace(165, 172, "__webpack_require__", None);
180-
replace_source.replace(173, 180, "/*! react */\"./node_modules/.pnpm/react@18.2.0/node_modules/react/index.js\"", None);
181-
replace_source.replace(183, 190, "__webpack_require__", None);
182-
replace_source.replace(191, 202, "/*! react-dom */\"./node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/index.js\"", None);
183-
replace_source.replace(205, 212, "__webpack_require__", None);
184-
replace_source.replace(213, 220, "/*! dayjs */\"./node_modules/.pnpm/dayjs@1.11.10/node_modules/dayjs/dayjs.min.js\"", None);
185-
replace_source.replace(363, 370, "exports", None);
186-
replace_source.replace(373, 385, "exports.antd", None);
187-
replace_source.replace(390, 397, "__webpack_require__", None);
188-
replace_source.replace(398, 405, "/*! react */\"./node_modules/.pnpm/react@18.2.0/node_modules/react/index.js\"", None);
189-
replace_source.replace(408, 415, "__webpack_require__", None);
190-
replace_source.replace(416, 427, "/*! react-dom */\"./node_modules/.pnpm/react-dom@18.2.0_react@18.2.0/node_modules/react-dom/index.js\"", None);
191-
replace_source.replace(430, 437, "__webpack_require__", None);
192-
replace_source.replace(438, 445, "/*! dayjs */\"./node_modules/.pnpm/dayjs@1.11.10/node_modules/dayjs/dayjs.min.js\"", None);
193-
replace_source.replace(494, 498, "this", None);
194-
195-
b.iter(|| {
196-
replace_source.map(&MapOptions::default());
197-
});
198-
}
199-
200171
fn benchmark_concat_generate_string_with_cache_as_key(b: &mut Bencher) {
201172
let sms_minify = SourceMapSource::new(SourceMapSourceOptions {
202173
value: HELLOWORLD_MIN_JS,
@@ -260,16 +231,14 @@ fn bench_rspack_sources(criterion: &mut Criterion) {
260231
);
261232
group
262233
.bench_function("concat_generate_base64", benchmark_concat_generate_base64);
234+
263235
group.bench_function(
264236
"concat_generate_string_with_cache",
265237
benchmark_concat_generate_string_with_cache,
266238
);
267239
group
268240
.bench_function("concat_generate_string", benchmark_concat_generate_string);
269-
group.bench_function(
270-
"replace_large_minified_source",
271-
benchmark_replace_large_minified_source,
272-
);
241+
273242
group.bench_function(
274243
"concat_generate_string_with_cache_as_key",
275244
benchmark_concat_generate_string_with_cache_as_key,
@@ -278,6 +247,10 @@ fn bench_rspack_sources(criterion: &mut Criterion) {
278247
"concat_generate_string_as_key",
279248
benchmark_concat_generate_string_as_key,
280249
);
250+
251+
group
252+
.bench_function("complex_replace_source", benchmark_complex_replace_source);
253+
281254
group.finish();
282255
}
283256

benches/bench_complex_replace_source.rs

Lines changed: 36722 additions & 0 deletions
Large diffs are not rendered by default.

src/cached_source.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,17 @@ impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
185185
&self,
186186
f: &mut std::fmt::Formatter<'_>,
187187
) -> Result<(), std::fmt::Error> {
188-
f.debug_struct("CachedSource")
189-
.field("inner", self.inner.as_ref())
190-
.field("cached_hash", self.cached_hash.as_ref())
191-
.field("cached_maps", &(!self.cached_maps.is_empty()))
192-
.finish()
188+
let indent = f.width().unwrap_or(0);
189+
let indent_str = format!("{:indent$}", "", indent = indent);
190+
191+
writeln!(f, "{indent_str}CachedSource::new(")?;
192+
writeln!(
193+
f,
194+
"{indent_str}{:indent$?}",
195+
self.inner,
196+
indent = indent + 2
197+
)?;
198+
write!(f, "{indent_str}).boxed()")
193199
}
194200
}
195201

src/concat_source.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,24 @@ use crate::{
5555
/// .unwrap()
5656
/// );
5757
/// ```
58-
#[derive(Debug, Default, Clone)]
58+
#[derive(Default, Clone)]
5959
pub struct ConcatSource {
6060
children: Vec<BoxSource>,
6161
}
6262

63+
impl std::fmt::Debug for ConcatSource {
64+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65+
let indent = f.width().unwrap_or(0);
66+
let indent_str = format!("{:indent$}", "", indent = indent);
67+
68+
writeln!(f, "{indent_str}ConcatSource::new(vec![")?;
69+
for child in self.children.iter() {
70+
writeln!(f, "{:indent$?},", child, indent = indent + 2)?;
71+
}
72+
write!(f, "{indent_str}]).boxed()")
73+
}
74+
}
75+
6376
impl ConcatSource {
6477
/// Create a [ConcatSource] with [Source]s.
6578
pub fn new<S, T>(sources: S) -> Self

src/original_source.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,13 @@ impl std::fmt::Debug for OriginalSource {
9595
&self,
9696
f: &mut std::fmt::Formatter<'_>,
9797
) -> Result<(), std::fmt::Error> {
98-
f.debug_struct("OriginalSource")
99-
.field("name", &self.name)
100-
.field("value", &self.value.chars().take(50).collect::<String>())
101-
.finish()
98+
let indent = f.width().unwrap_or(0);
99+
let indent_str = format!("{:indent$}", "", indent = indent);
100+
101+
writeln!(f, "{indent_str}OriginalSource::new(")?;
102+
writeln!(f, "{indent_str} {:?},", self.value)?;
103+
writeln!(f, "{indent_str} {:?},", self.name)?;
104+
write!(f, "{indent_str}).boxed()")
102105
}
103106
}
104107

src/raw_source.rs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,22 @@ impl std::fmt::Debug for RawSource {
182182
&self,
183183
f: &mut std::fmt::Formatter<'_>,
184184
) -> Result<(), std::fmt::Error> {
185-
let mut d = f.debug_struct("RawSource");
186-
match &self.value {
187-
RawValue::Buffer(buffer) => {
188-
d.field(
189-
"buffer",
190-
&buffer.iter().take(50).copied().collect::<Vec<u8>>(),
191-
);
192-
}
193-
RawValue::String(string) => {
194-
d.field("source", &string.chars().take(50).collect::<String>());
195-
}
185+
let indent = f.width().unwrap_or(0);
186+
let indent_str = format!("{:indent$}", "", indent = indent);
187+
188+
if self.is_buffer() {
189+
write!(
190+
f,
191+
"{indent_str}RawSource::from({:?}).boxed()",
192+
self.buffer()
193+
)
194+
} else {
195+
write!(
196+
f,
197+
"{indent_str}RawSource::from_static({:?}).boxed()",
198+
self.source()
199+
)
196200
}
197-
d.finish()
198201
}
199202
}
200203

@@ -311,9 +314,13 @@ impl std::fmt::Debug for RawStringSource {
311314
&self,
312315
f: &mut std::fmt::Formatter<'_>,
313316
) -> Result<(), std::fmt::Error> {
314-
let mut d = f.debug_tuple("RawStringSource");
315-
d.field(&self.0.chars().take(50).collect::<String>());
316-
d.finish()
317+
let indent = f.width().unwrap_or(0);
318+
let indent_str = format!("{:indent$}", "", indent = indent);
319+
write!(
320+
f,
321+
"{indent_str}RawStringSource::from_static({:?}).boxed()",
322+
self.0.as_ref()
323+
)
317324
}
318325
}
319326

@@ -418,9 +425,13 @@ impl std::fmt::Debug for RawBufferSource {
418425
&self,
419426
f: &mut std::fmt::Formatter<'_>,
420427
) -> Result<(), std::fmt::Error> {
421-
let mut d = f.debug_tuple("RawBufferSource");
422-
d.field(&self.value.iter().take(50).copied().collect::<Vec<u8>>());
423-
d.finish()
428+
let indent = f.width().unwrap_or(0);
429+
let indent_str = format!("{:indent$}", "", indent = indent);
430+
write!(
431+
f,
432+
"{indent_str}RawBufferSource::from({:?}).boxed()",
433+
self.value
434+
)
424435
}
425436
}
426437

src/replace_source.rs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
},
1818
linear_map::LinearMap,
1919
rope::Rope,
20+
with_indices::WithIndices,
2021
MapOptions, Mapping, OriginalLocation, Source, SourceMap,
2122
};
2223

@@ -282,40 +283,58 @@ impl<T: std::fmt::Debug> std::fmt::Debug for ReplaceSource<T> {
282283
&self,
283284
f: &mut std::fmt::Formatter<'_>,
284285
) -> Result<(), std::fmt::Error> {
285-
f.debug_struct("ReplaceSource")
286-
.field("inner", self.inner.as_ref())
287-
.field(
288-
"replacements",
289-
&self.replacements.iter().take(3).collect::<Vec<_>>(),
290-
)
291-
.field("is_sorted", &self.is_sorted.load(Ordering::SeqCst))
292-
.finish()
286+
let indent = f.width().unwrap_or(0);
287+
let indent_str = format!("{:indent$}", "", indent = indent);
288+
289+
writeln!(f, "{indent_str}{{")?;
290+
writeln!(f, "{indent_str} let mut source = ReplaceSource::new(")?;
291+
writeln!(f, "{:indent$?}", &self.inner, indent = indent + 4)?;
292+
writeln!(f, "{indent_str} );")?;
293+
for repl in self.sorted_replacement() {
294+
match repl.enforce {
295+
ReplacementEnforce::Pre => {
296+
writeln!(
297+
f,
298+
"{indent_str} source.replace_with_enforce({:#?}, {:#?}, {:#?}, {:#?}, ReplacementEnforce::Pre);",
299+
repl.start, repl.end, repl.content, repl.name
300+
)?;
301+
}
302+
ReplacementEnforce::Normal => {
303+
writeln!(
304+
f,
305+
"{indent_str} source.replace({:#?}, {:#?}, {:#?}, {:#?});",
306+
repl.start, repl.end, repl.content, repl.name
307+
)?;
308+
}
309+
ReplacementEnforce::Post => {
310+
writeln!(
311+
f,
312+
"{indent_str} source.replace_with_enforce({:#?}, {:#?}, {:#?}, {:#?}, ReplacementEnforce::Post);",
313+
repl.start, repl.end, repl.content, repl.name
314+
)?;
315+
}
316+
}
317+
}
318+
writeln!(f, "{indent_str} source.boxed()")?;
319+
write!(f, "{indent_str}}}")
293320
}
294321
}
295322

296323
enum SourceContent<'a> {
297324
Raw(Rope<'a>),
298-
Lines(Vec<Rope<'a>>),
325+
Lines(Vec<WithIndices<'a, Rope<'a>>>),
299326
}
300327

301-
fn check_content_at_position(
302-
lines: &[Rope],
328+
fn check_content_at_position<'a>(
329+
lines: &[WithIndices<'a, Rope<'a>>],
303330
line: u32,
304331
column: u32,
305332
expected: Rope, // FIXME: memory
306333
) -> bool {
307334
if let Some(line) = lines.get(line as usize - 1) {
308-
match line
309-
.char_indices()
310-
.nth(column as usize)
311-
.map(|(byte_index, _)| byte_index)
312-
{
313-
Some(byte_index) => {
314-
line.get_byte_slice(byte_index..byte_index + expected.len())
315-
== Some(expected)
316-
}
317-
None => false,
318-
}
335+
line
336+
.substring(column as usize, usize::MAX)
337+
.starts_with(&expected)
319338
} else {
320339
false
321340
}
@@ -378,7 +397,9 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
378397
{
379398
match source_content {
380399
SourceContent::Raw(source) => {
381-
let lines = split_into_lines(source).collect::<Vec<_>>();
400+
let lines = split_into_lines(source)
401+
.map(WithIndices::new)
402+
.collect::<Vec<_>>();
382403
let matched =
383404
check_content_at_position(&lines, line, column, expected_chunk);
384405
*source_content = SourceContent::Lines(lines);
@@ -1220,4 +1241,26 @@ return <div>{data.foo}</div>
12201241
source2.replace(18, 19, "))", None);
12211242
assert_eq!(source2.source(), "export default foo)));aaa");
12221243
}
1244+
1245+
#[test]
1246+
fn debug() {
1247+
let mut source =
1248+
ReplaceSource::new(OriginalSource::new("hello", "file.txt").boxed());
1249+
source.replace(0, 0, "println!(\"", None);
1250+
source.replace(5, 5, "\")", None);
1251+
assert_eq!(
1252+
format!("{:?}", source),
1253+
r#"{
1254+
let mut source = ReplaceSource::new(
1255+
OriginalSource::new(
1256+
"hello",
1257+
"file.txt",
1258+
).boxed()
1259+
);
1260+
source.replace(0, 0, "println!(\"", None);
1261+
source.replace(5, 5, "\")", None);
1262+
source.boxed()
1263+
}"#
1264+
);
1265+
}
12231266
}

0 commit comments

Comments
 (0)