Skip to content

Commit 98db0a7

Browse files
authored
perf: using Arc<str> to share source content between source map (#196)
1 parent d32c0cc commit 98db0a7

File tree

10 files changed

+129
-100
lines changed

10 files changed

+129
-100
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ memchr = "2.7.4"
4040
codspeed-criterion-compat = { version = "2.7.2", default-features = false, optional = true }
4141
static_assertions = "1.1.0"
4242
simd-json = "0.14.3"
43+
self_cell = "1.2.1"
4344

4445
[dev-dependencies]
4546
twox-hash = "2.1.0"

benches/bench.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ fn benchmark_concat_generate_string(b: &mut Bencher) {
6262
value: HELLOWORLD_MIN_JS,
6363
name: "helloworld.min.js",
6464
source_map: SourceMap::from_json(HELLOWORLD_MIN_JS_MAP).unwrap(),
65-
original_source: Some(HELLOWORLD_JS.to_string()),
65+
original_source: Some(HELLOWORLD_JS.to_string().into()),
6666
inner_source_map: Some(SourceMap::from_json(HELLOWORLD_JS_MAP).unwrap()),
6767
remove_original_source: false,
6868
});
@@ -92,7 +92,7 @@ fn benchmark_concat_generate_string_with_cache(b: &mut Bencher) {
9292
value: HELLOWORLD_MIN_JS,
9393
name: "helloworld.min.js",
9494
source_map: SourceMap::from_json(HELLOWORLD_MIN_JS_MAP).unwrap(),
95-
original_source: Some(HELLOWORLD_JS.to_string()),
95+
original_source: Some(HELLOWORLD_JS.to_string().into()),
9696
inner_source_map: Some(SourceMap::from_json(HELLOWORLD_JS_MAP).unwrap()),
9797
remove_original_source: false,
9898
});
@@ -121,7 +121,7 @@ fn benchmark_cached_source_hash(b: &mut Bencher) {
121121
value: HELLOWORLD_MIN_JS,
122122
name: "helloworld.min.js",
123123
source_map: SourceMap::from_json(HELLOWORLD_MIN_JS_MAP).unwrap(),
124-
original_source: Some(HELLOWORLD_JS.to_string()),
124+
original_source: Some(HELLOWORLD_JS.to_string().into()),
125125
inner_source_map: Some(SourceMap::from_json(HELLOWORLD_JS_MAP).unwrap()),
126126
remove_original_source: false,
127127
});

src/helpers.rs

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{
33
cell::{OnceCell, RefCell},
44
marker::PhantomData,
55
ops::Range,
6+
sync::Arc,
67
};
78

89
use rustc_hash::FxHashMap as HashMap;
@@ -12,21 +13,18 @@ use crate::{
1213
encoder::create_encoder,
1314
linear_map::LinearMap,
1415
source::{Mapping, OriginalLocation},
16+
source_content_lines::SourceContentLines,
1517
with_indices::WithIndices,
1618
MapOptions, Rope, SourceMap,
1719
};
1820

19-
// Adding this type because sourceContentLine not happy
20-
type InnerSourceContentLine<'a, 'b> =
21-
RefCell<LinearMap<OnceCell<Option<Vec<WithIndices<'a, Rope<'b>>>>>>>;
22-
2321
pub fn get_map<'a, S: StreamChunks>(
2422
stream: &'a S,
2523
options: &'a MapOptions,
2624
) -> Option<SourceMap> {
2725
let mut mappings_encoder = create_encoder(options.columns);
2826
let mut sources: Vec<String> = Vec::new();
29-
let mut sources_content: Vec<String> = Vec::new();
27+
let mut sources_content: Vec<Arc<str>> = Vec::new();
3028
let mut names: Vec<String> = Vec::new();
3129

3230
stream.stream_chunks(
@@ -47,9 +45,9 @@ pub fn get_map<'a, S: StreamChunks>(
4745
sources[source_index] = source.to_string();
4846
if let Some(source_content) = source_content {
4947
if sources_content.len() <= source_index {
50-
sources_content.resize(source_index + 1, "".to_string());
48+
sources_content.resize(source_index + 1, "".into());
5149
}
52-
sources_content[source_index] = source_content.to_string();
50+
sources_content[source_index] = source_content.clone();
5351
}
5452
},
5553
// on_name
@@ -82,8 +80,9 @@ pub trait StreamChunks {
8280
pub type OnChunk<'a, 'b> = &'a mut dyn FnMut(Option<Rope<'b>>, Mapping);
8381

8482
/// [OnSource] abstraction, see [webpack-sources onSource](https://github.com/webpack/webpack-sources/blob/9f98066311d53a153fdc7c633422a1d086528027/lib/helpers/streamChunks.js#L13).
83+
///
8584
pub type OnSource<'a, 'b> =
86-
&'a mut dyn FnMut(u32, Cow<'b, str>, Option<Rope<'b>>);
85+
&'a mut dyn FnMut(u32, Cow<'b, str>, Option<&'b Arc<str>>);
8786

8887
/// [OnName] abstraction, see [webpack-sources onName](https://github.com/webpack/webpack-sources/blob/9f98066311d53a153fdc7c633422a1d086528027/lib/helpers/streamChunks.js#L13).
8988
pub type OnName<'a, 'b> = &'a mut dyn FnMut(u32, Cow<'b, str>);
@@ -195,8 +194,6 @@ where
195194
}
196195
}
197196

198-
const EMPTY_ROPE: Rope = Rope::new();
199-
200197
/// Split the string with a needle, each string will contain the needle.
201198
///
202199
/// Copied and modified from https://github.com/rust-lang/cargo/blob/30efe860c0e4adc1a6d7057ad223dc6e47d34edf/src/cargo/sources/registry/index.rs#L1048-L1072
@@ -371,7 +368,7 @@ where
371368
on_source(
372369
i as u32,
373370
get_source(source_map, source),
374-
source_map.get_source_content(i).map(Rope::from),
371+
source_map.get_source_content(i),
375372
)
376373
}
377374
for (i, name) in source_map.names().iter().enumerate() {
@@ -435,7 +432,7 @@ where
435432
on_source(
436433
i as u32,
437434
get_source(source_map, source),
438-
source_map.get_source_content(i).map(Rope::from),
435+
source_map.get_source_content(i),
439436
)
440437
}
441438
for (i, name) in source_map.names().iter().enumerate() {
@@ -585,7 +582,7 @@ where
585582
on_source(
586583
i as u32,
587584
get_source(source_map, source),
588-
source_map.get_source_content(i).map(Rope::from),
585+
source_map.get_source_content(i),
589586
)
590587
}
591588
let final_line = if result.generated_column == 0 {
@@ -633,7 +630,7 @@ where
633630
on_source(
634631
i as u32,
635632
get_source(source_map, source),
636-
source_map.get_source_content(i).map(Rope::from),
633+
source_map.get_source_content(i),
637634
)
638635
}
639636
let mut current_generated_line = 1;
@@ -706,14 +703,14 @@ struct SourceMapLineData<'a> {
706703
}
707704

708705
type InnerSourceIndexValueMapping<'a> =
709-
LinearMap<(Cow<'a, str>, Option<Rope<'a>>)>;
706+
LinearMap<(Cow<'a, str>, Option<&'a Arc<str>>)>;
710707

711708
#[allow(clippy::too_many_arguments)]
712709
pub fn stream_chunks_of_combined_source_map<'a, S>(
713710
source: S,
714711
source_map: &'a SourceMap,
715712
inner_source_name: &'a str,
716-
inner_source: Option<Rope<'a>>,
713+
inner_source: Option<&'a Arc<str>>,
717714
inner_source_map: &'a SourceMap,
718715
remove_inner_source: bool,
719716
on_chunk: OnChunk<'_, 'a>,
@@ -725,7 +722,7 @@ where
725722
S: SourceText<'a> + 'a,
726723
{
727724
let on_source = RefCell::new(on_source);
728-
let inner_source: RefCell<Option<Rope<'a>>> = RefCell::new(inner_source);
725+
let inner_source: RefCell<Option<&Arc<str>>> = RefCell::new(inner_source);
729726
let source_mapping: RefCell<HashMap<Cow<str>, u32>> =
730727
RefCell::new(HashMap::default());
731728
let mut name_mapping: HashMap<Cow<str>, u32> = HashMap::default();
@@ -740,10 +737,11 @@ where
740737
RefCell::new(LinearMap::default());
741738
let inner_source_index_value_mapping: RefCell<InnerSourceIndexValueMapping> =
742739
RefCell::new(LinearMap::default());
743-
let inner_source_contents: RefCell<LinearMap<Option<Rope<'a>>>> =
744-
RefCell::new(LinearMap::default());
745-
let inner_source_content_lines: InnerSourceContentLine =
740+
let inner_source_contents: RefCell<LinearMap<Option<Arc<str>>>> =
746741
RefCell::new(LinearMap::default());
742+
let inner_source_content_lines: RefCell<
743+
LinearMap<OnceCell<Option<SourceContentLines>>>,
744+
> = RefCell::new(LinearMap::default());
747745
let inner_name_index_mapping: RefCell<LinearMap<i64>> =
748746
RefCell::new(LinearMap::default());
749747
let inner_name_index_value_mapping: RefCell<LinearMap<Cow<str>>> =
@@ -828,11 +826,9 @@ where
828826
Some(once_cell) => once_cell.get_or_init(|| {
829827
let inner_source_contents = inner_source_contents.borrow();
830828
match inner_source_contents.get(&inner_source_index) {
831-
Some(Some(source_content)) => Some(
832-
split_into_lines(source_content)
833-
.map(WithIndices::new)
834-
.collect(),
835-
),
829+
Some(Some(source_content)) => {
830+
Some(SourceContentLines::from(source_content.clone()))
831+
}
836832
_ => None,
837833
}
838834
}),
@@ -848,8 +844,9 @@ where
848844
});
849845
if let Some(original_chunk) = original_chunk {
850846
if original_chunk.len() <= inner_chunk.len()
851-
&& inner_chunk.get_byte_slice(..original_chunk.len())
852-
== Some(original_chunk)
847+
&& inner_chunk
848+
.get_byte_slice(..original_chunk.len())
849+
.is_some_and(|slice| slice == original_chunk)
853850
{
854851
inner_original_column += location_in_chunk;
855852
inner_name_index = -1;
@@ -928,11 +925,9 @@ where
928925
Some(once_cell) => once_cell.get_or_init(|| {
929926
let inner_source_contents = inner_source_contents.borrow();
930927
match inner_source_contents.get(&inner_source_index) {
931-
Some(Some(source_content)) => Some(
932-
split_into_lines(source_content)
933-
.map(WithIndices::new)
934-
.collect(),
935-
),
928+
Some(Some(source_content)) => {
929+
Some(SourceContentLines::from(source_content.clone()))
930+
}
936931
_ => None,
937932
}
938933
}),
@@ -945,12 +940,12 @@ where
945940
name_index_value_mapping.get(&name_index).cloned().unwrap();
946941
let original_name = original_source_lines
947942
.get(inner_original_line as usize - 1)
948-
.map_or(EMPTY_ROPE, |i| {
943+
.map_or("", |i| {
949944
let start = inner_original_column as usize;
950945
let end = start + name.len();
951946
i.substring(start, end)
952947
});
953-
if Rope::from(&name) == original_name {
948+
if name == original_name {
954949
let mut name_index_mapping = name_index_mapping.borrow_mut();
955950
final_name_index =
956951
name_index_mapping.get(&name_index).copied().unwrap_or(-2);
@@ -1015,7 +1010,7 @@ where
10151010
on_source.borrow_mut()(
10161011
len,
10171012
Cow::Borrowed(inner_source_name),
1018-
inner_source.borrow().clone(),
1013+
*inner_source.borrow(),
10191014
);
10201015
global_index = Some(len);
10211016
}
@@ -1089,13 +1084,13 @@ where
10891084
*inner_source_index.borrow_mut() = i as i64;
10901085
let mut inner_source = inner_source.borrow_mut();
10911086
if let Some(inner_source) = inner_source.as_ref() {
1092-
source_content = Some(inner_source.clone());
1087+
source_content = Some(inner_source);
10931088
} else {
1094-
*inner_source = source_content.clone();
1089+
*inner_source = source_content;
10951090
}
10961091
source_index_mapping.borrow_mut().insert(i, -2);
10971092
stream_chunks_of_source_map(
1098-
source_content.unwrap(),
1093+
source_content.unwrap().as_ref(),
10991094
inner_source_map,
11001095
&mut |chunk, mapping| {
11011096
let mut inner_source_map_line_data =
@@ -1151,7 +1146,7 @@ where
11511146
&mut |i, source, source_content| {
11521147
inner_source_contents
11531148
.borrow_mut()
1154-
.insert(i, source_content.clone());
1149+
.insert(i, source_content.cloned());
11551150
inner_source_content_lines
11561151
.borrow_mut()
11571152
.insert(i, Default::default());
@@ -1200,7 +1195,7 @@ pub fn stream_and_get_source_and_map<'a, S: StreamChunks>(
12001195
) -> (GeneratedInfo, Option<SourceMap>) {
12011196
let mut mappings_encoder = create_encoder(options.columns);
12021197
let mut sources: Vec<String> = Vec::new();
1203-
let mut sources_content: Vec<String> = Vec::new();
1198+
let mut sources_content: Vec<Arc<str>> = Vec::new();
12041199
let mut names: Vec<String> = Vec::new();
12051200

12061201
let generated_info = input_source.stream_chunks(
@@ -1215,11 +1210,11 @@ pub fn stream_and_get_source_and_map<'a, S: StreamChunks>(
12151210
sources.push("".into());
12161211
}
12171212
sources[source_index2] = source.to_string();
1218-
if let Some(ref source_content) = source_content {
1213+
if let Some(source_content) = source_content {
12191214
while sources_content.len() <= source_index2 {
12201215
sources_content.push("".into());
12211216
}
1222-
sources_content[source_index2] = source_content.to_string();
1217+
sources_content[source_index2] = source_content.clone();
12231218
}
12241219
on_source(source_index, source, source_content);
12251220
},

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod raw_source;
1212
mod replace_source;
1313
mod rope;
1414
mod source;
15+
mod source_content_lines;
1516
mod source_map_source;
1617
mod with_indices;
1718

src/original_source.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
borrow::Cow,
33
hash::{Hash, Hasher},
4+
sync::Arc,
45
};
56

67
use crate::{
@@ -36,13 +37,13 @@ use crate::{
3637
/// ```
3738
#[derive(Clone, Eq)]
3839
pub struct OriginalSource {
39-
value: String,
40+
value: Arc<str>,
4041
name: String,
4142
}
4243

4344
impl OriginalSource {
4445
/// Create a [OriginalSource].
45-
pub fn new(value: impl Into<String>, name: impl Into<String>) -> Self {
46+
pub fn new(value: impl Into<Arc<str>>, name: impl Into<String>) -> Self {
4647
Self {
4748
value: value.into(),
4849
name: name.into(),
@@ -56,7 +57,7 @@ impl Source for OriginalSource {
5657
}
5758

5859
fn rope(&self) -> Rope<'_> {
59-
Rope::from(&self.value)
60+
Rope::from(self.value.as_ref())
6061
}
6162

6263
fn buffer(&self) -> Cow<[u8]> {
@@ -113,7 +114,7 @@ impl StreamChunks for OriginalSource {
113114
on_source: OnSource<'_, 'a>,
114115
_on_name: OnName,
115116
) -> crate::helpers::GeneratedInfo {
116-
on_source(0, Cow::Borrowed(&self.name), Some(Rope::from(&self.value)));
117+
on_source(0, Cow::Borrowed(&self.name), Some(&self.value));
117118
if options.columns {
118119
// With column info we need to read all lines and split them
119120
let mut line = 1;
@@ -200,7 +201,7 @@ impl StreamChunks for OriginalSource {
200201
// we need to split source by lines
201202
let mut line = 1;
202203
let mut last_line = None;
203-
for l in split_into_lines(&self.value.as_str()) {
204+
for l in split_into_lines(&self.value.as_ref()) {
204205
on_chunk(
205206
(!options.final_source).then_some(l.into_rope()),
206207
Mapping {
@@ -250,13 +251,10 @@ mod tests {
250251
assert_eq!(result_text.into_string_lossy(), "Line1\n\nLine3\n");
251252
assert_eq!(result_map.sources(), &["file.js".to_string()]);
252253
assert_eq!(result_list_map.sources(), ["file.js".to_string()]);
253-
assert_eq!(
254-
result_map.sources_content(),
255-
["Line1\n\nLine3\n".to_string()],
256-
);
254+
assert_eq!(result_map.sources_content(), ["Line1\n\nLine3\n".into()],);
257255
assert_eq!(
258256
result_list_map.sources_content(),
259-
["Line1\n\nLine3\n".to_string()],
257+
["Line1\n\nLine3\n".into()],
260258
);
261259
assert_eq!(result_map.mappings(), "AAAA;;AAEA");
262260
assert_eq!(result_list_map.mappings(), "AAAA;AACA;AACA");

0 commit comments

Comments
 (0)