From 926bc5e83cfea095da6abc3303b0261e1263a39d Mon Sep 17 00:00:00 2001 From: Cong-Cong <dacongsama@live.com> Date: Thu, 5 Dec 2024 18:32:21 +0800 Subject: [PATCH 1/2] perf: cow map --- src/cached_source.rs | 36 ++++++++++++++++++++++++++----- src/concat_source.rs | 22 +++++++++---------- src/original_source.rs | 4 ++-- src/raw_source.rs | 6 +++--- src/replace_source.rs | 4 ++-- src/source.rs | 8 +++---- src/source_map_source.rs | 46 +++++++++++++++++++++++----------------- tests/compat_source.rs | 6 +++--- 8 files changed, 82 insertions(+), 50 deletions(-) diff --git a/src/cached_source.rs b/src/cached_source.rs index 7b8ef2dc..3e307184 100644 --- a/src/cached_source.rs +++ b/src/cached_source.rs @@ -94,13 +94,39 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> { self.source().len() } - fn map(&self, options: &MapOptions) -> Option<SourceMap> { + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>> { if let Some(map) = self.cached_maps.get(options) { - map.clone() + match map.as_ref() { + Some(map) => { + #[allow(unsafe_code)] + // SAFETY: We guarantee that once a `SourceMap` is stored in the cache, it will never be removed. + // Therefore, even if we force its lifetime to be longer, the reference remains valid. + // This is based on the following assumptions: + // 1. `SourceMap` will be valid for the entire duration of the application. + // 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety. + let map = unsafe { std::mem::transmute::<&SourceMap, &'static SourceMap>(map) }; + Some(Cow::Borrowed(map)) + }, + None => None, + } } else { let map = self.inner.map(options); - self.cached_maps.insert(options.clone(), map.clone()); - map + self + .cached_maps + .insert(options.clone(), map.map(|m| m.into_owned())); + match self.cached_maps.get(options).unwrap().as_ref() { + Some(map) => { + #[allow(unsafe_code)] + // SAFETY: We guarantee that once a `SourceMap` is stored in the cache, it will never be removed. + // Therefore, even if we force its lifetime to be longer, the reference remains valid. + // This is based on the following assumptions: + // 1. `SourceMap` will be valid for the entire duration of the application. + // 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety. + let map = unsafe { std::mem::transmute::<&SourceMap, &'static SourceMap>(map) }; + Some(Cow::Borrowed(map)) + }, + None => None, + } } } @@ -254,7 +280,7 @@ mod tests { ); assert_eq!( *clone.cached_maps.get(&map_options).unwrap().value(), - source.map(&map_options) + source.map(&map_options).map(|map| map.into_owned()) ); } diff --git a/src/concat_source.rs b/src/concat_source.rs index 7435fcb6..230785ca 100644 --- a/src/concat_source.rs +++ b/src/concat_source.rs @@ -39,7 +39,7 @@ use crate::{ /// "Hello World\nconsole.log('test');\nconsole.log('test2');\nHello2\n" /// ); /// assert_eq!( -/// source.map(&MapOptions::new(false)).unwrap(), +/// source.map(&MapOptions::new(false)).unwrap().into_owned(), /// SourceMap::from_json( /// r#"{ /// "version": 3, @@ -127,8 +127,8 @@ impl Source for ConcatSource { self.children().iter().map(|child| child.size()).sum() } - fn map(&self, options: &MapOptions) -> Option<SourceMap> { - get_map(self, options) + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>> { + get_map(self, options).map(Cow::Owned) } fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { @@ -347,7 +347,7 @@ mod tests { assert_eq!(source.size(), 62); assert_eq!(source.source(), expected_source); assert_eq!( - source.map(&MapOptions::new(false)).unwrap(), + source.map(&MapOptions::new(false)).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -363,7 +363,7 @@ mod tests { .unwrap() ); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -397,7 +397,7 @@ mod tests { assert_eq!(source.size(), 62); assert_eq!(source.source(), expected_source); assert_eq!( - source.map(&MapOptions::new(false)).unwrap(), + source.map(&MapOptions::new(false)).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -413,7 +413,7 @@ mod tests { .unwrap() ); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -447,7 +447,7 @@ mod tests { assert_eq!(source.size(), 62); assert_eq!(source.source(), expected_source); assert_eq!( - source.map(&MapOptions::new(false)).unwrap(), + source.map(&MapOptions::new(false)).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -463,7 +463,7 @@ mod tests { .unwrap() ); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -513,7 +513,7 @@ mod tests { assert_eq!(source.buffer(), expected_source.as_bytes()); let map = source.map(&MapOptions::new(false)).unwrap(); - assert_eq!(map, expected_map1); + assert_eq!(map.into_owned(), expected_map1); // TODO: test hash } @@ -549,7 +549,7 @@ mod tests { ]); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAA,K,CCAA,M;ADAA;;ACAA", diff --git a/src/original_source.rs b/src/original_source.rs index b73c275d..b0f57d92 100644 --- a/src/original_source.rs +++ b/src/original_source.rs @@ -63,8 +63,8 @@ impl Source for OriginalSource { self.value.len() } - fn map(&self, options: &MapOptions) -> Option<SourceMap> { - get_map(self, options) + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>> { + get_map(self, options).map(Cow::Owned) } fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { diff --git a/src/raw_source.rs b/src/raw_source.rs index 388ea69d..4db5205a 100644 --- a/src/raw_source.rs +++ b/src/raw_source.rs @@ -137,7 +137,7 @@ impl Source for RawSource { } } - fn map(&self, _: &MapOptions) -> Option<SourceMap> { + fn map(&self, _: &MapOptions) -> Option<Cow<SourceMap>> { None } @@ -274,7 +274,7 @@ impl Source for RawStringSource { self.0.len() } - fn map(&self, _: &MapOptions) -> Option<SourceMap> { + fn map(&self, _: &MapOptions) -> Option<Cow<SourceMap>> { None } @@ -373,7 +373,7 @@ impl Source for RawBufferSource { self.value.len() } - fn map(&self, _: &MapOptions) -> Option<SourceMap> { + fn map(&self, _: &MapOptions) -> Option<Cow<SourceMap>> { None } diff --git a/src/replace_source.rs b/src/replace_source.rs index 378cd841..7673ca17 100644 --- a/src/replace_source.rs +++ b/src/replace_source.rs @@ -216,13 +216,13 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for ReplaceSource<T> { self.source().len() } - fn map(&self, options: &crate::MapOptions) -> Option<SourceMap> { + fn map(&self, options: &crate::MapOptions) -> Option<Cow<SourceMap>> { let replacements = self.replacements.lock().unwrap(); if replacements.is_empty() { return self.inner.map(options); } drop(replacements); - get_map(self, options) + get_map(self, options).map(Cow::Owned) } fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { diff --git a/src/source.rs b/src/source.rs index 25adbb5c..a43e555b 100644 --- a/src/source.rs +++ b/src/source.rs @@ -39,7 +39,7 @@ pub trait Source: fn size(&self) -> usize; /// Get the [SourceMap]. - fn map(&self, options: &MapOptions) -> Option<SourceMap>; + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>>; /// Update hash based on the source. fn update_hash(&self, state: &mut dyn Hasher) { @@ -63,7 +63,7 @@ impl Source for BoxSource { self.as_ref().size() } - fn map(&self, options: &MapOptions) -> Option<SourceMap> { + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>> { self.as_ref().map(options) } @@ -370,8 +370,8 @@ impl SourceMap { } /// Generate source map to a json string. - pub fn to_json(self) -> Result<String> { - let json = simd_json::serde::to_string(&self)?; + pub fn to_json(&self) -> Result<String> { + let json = simd_json::serde::to_string(self)?; Ok(json) } diff --git a/src/source_map_source.rs b/src/source_map_source.rs index 07b32a05..0613e4ec 100644 --- a/src/source_map_source.rs +++ b/src/source_map_source.rs @@ -100,11 +100,11 @@ impl Source for SourceMapSource { self.value.len() } - fn map(&self, options: &MapOptions) -> Option<SourceMap> { + fn map(&self, options: &MapOptions) -> Option<Cow<SourceMap>> { if self.inner_source_map.is_none() { - return Some(self.source_map.clone()); + return Some(Cow::Borrowed(&self.source_map)); } - get_map(self, options) + get_map(self, options).map(Cow::Owned) } fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { @@ -215,7 +215,9 @@ mod tests { name: "text", source_map: source_r_map.clone(), original_source: Some(inner_source.source().to_string()), - inner_source_map: inner_source.map(&MapOptions::default()), + inner_source_map: inner_source + .map(&MapOptions::default()) + .map(|map| map.into_owned()), remove_original_source: false, }); let sms2 = SourceMapSource::new(SourceMapSourceOptions { @@ -223,7 +225,9 @@ mod tests { name: "text", source_map: source_r_map, original_source: Some(inner_source.source().to_string()), - inner_source_map: inner_source.map(&MapOptions::default()), + inner_source_map: inner_source + .map(&MapOptions::default()) + .map(|map| map.into_owned()), remove_original_source: true, }); let expected_content = @@ -231,7 +235,7 @@ mod tests { assert_eq!(sms1.source(), expected_content); assert_eq!(sms2.source(), expected_content); assert_eq!( - sms1.map(&MapOptions::default()).unwrap(), + sms1.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "YAAAA,K,CAAMC;AACN,O,MAAU;ACCC,O,CAAM", @@ -247,7 +251,7 @@ mod tests { .unwrap(), ); assert_eq!( - sms2.map(&MapOptions::default()).unwrap(), + sms2.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "YAAAA,K,CAAMC;AACN,O,MAAU", @@ -300,7 +304,7 @@ mod tests { let source = ConcatSource::new(sources); assert_eq!(source.source(), "hi world\nhi world\nhi world\n"); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAA;;ACAA,CAAC,CAAI", @@ -313,7 +317,7 @@ mod tests { .unwrap() ); assert_eq!( - source.map(&MapOptions::new(false)).unwrap(), + source.map(&MapOptions::new(false)).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAA;;ACAA", @@ -423,11 +427,11 @@ mod tests { } test_cached!(source, |s: &dyn Source| s.source().to_string()); - test_cached!(source, |s: &dyn Source| s.map(&MapOptions::default())); + test_cached!(source, |s: &dyn Source| s.map(&MapOptions::default()).map(|m| m.into_owned())); test_cached!(source, |s: &dyn Source| s.map(&MapOptions { columns: false, final_source: true - })); + }).map(|m| m.into_owned())); } #[test] @@ -458,7 +462,7 @@ mod tests { remove_original_source: false, }); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAA", @@ -502,7 +506,7 @@ mod tests { assert_eq!(source.source(), "Message: H W!"); assert_eq!(source.size(), 13); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAAA,SCAA,ECAMC,C", @@ -555,7 +559,7 @@ mod tests { }); let map = source.map(&MapOptions::default()).unwrap(); assert_eq!( - map, + map.into_owned(), SourceMap::from_json( r#"{ "version": 3, @@ -574,7 +578,7 @@ mod tests { let source = SourceMapSource::new(WithoutOriginalOptions { value: "console.log('a')\n", name: "a.js", - source_map: original.map(&MapOptions::new(false)).unwrap(), + source_map: original.map(&MapOptions::new(false)).unwrap().into_owned(), }); let source = ConcatSource::new([ RawSource::from("\n").boxed(), @@ -607,8 +611,10 @@ mod tests { }"#, ) .unwrap(); - let inner_source_map = - inner_source.map(&MapOptions::default()).map(|mut map| { + let inner_source_map = inner_source + .map(&MapOptions::default()) + .map(|map| { + let mut map = map.into_owned(); map.set_source_root(Some("/path/to/folder/".to_string())); map }); @@ -621,7 +627,7 @@ mod tests { remove_original_source: false, }); assert_eq!( - sms.map(&MapOptions::default()).unwrap(), + sms.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "YAAAA,K,CAAMC;AACN,O,MAAU;ACCC,O,CAAM", @@ -668,7 +674,7 @@ mod tests { remove_original_source: false, }); assert_eq!( - source.map(&MapOptions::new(false)).unwrap(), + source.map(&MapOptions::new(false)).unwrap().into_owned(), SourceMap::from_json( r#"{ "mappings": "AAAA", @@ -710,7 +716,7 @@ mod tests { remove_original_source: false, }); assert_eq!( - source.map(&MapOptions::default()).unwrap(), + source.map(&MapOptions::default()).unwrap().into_owned(), SourceMap::from_json( r#"{ "version": 3, diff --git a/tests/compat_source.rs b/tests/compat_source.rs index 9079d26c..dc8a1ad4 100644 --- a/tests/compat_source.rs +++ b/tests/compat_source.rs @@ -24,8 +24,8 @@ impl Source for CompatSource { 42 } - fn map(&self, _options: &MapOptions) -> Option<SourceMap> { - self.1.clone() + fn map(&self, _options: &MapOptions) -> Option<Cow<SourceMap>> { + self.1.as_ref().map(Cow::Borrowed) } fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { @@ -116,5 +116,5 @@ fn should_generate_correct_source_map() { .unwrap(); assert_eq!(source, expected_source); - assert_eq!(map, expected_source_map) + assert_eq!(map.into_owned(), expected_source_map) } From a144ae6afb4aa3d2f1cd48c703cfd099e7059423 Mon Sep 17 00:00:00 2001 From: Cong-Cong <dacongsama@live.com> Date: Thu, 5 Dec 2024 18:34:01 +0800 Subject: [PATCH 2/2] fix: fmt --- src/cached_source.rs | 12 ++++++++---- src/source_map_source.rs | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/cached_source.rs b/src/cached_source.rs index 3e307184..876f4473 100644 --- a/src/cached_source.rs +++ b/src/cached_source.rs @@ -104,9 +104,11 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> { // This is based on the following assumptions: // 1. `SourceMap` will be valid for the entire duration of the application. // 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety. - let map = unsafe { std::mem::transmute::<&SourceMap, &'static SourceMap>(map) }; + let map = unsafe { + std::mem::transmute::<&SourceMap, &'static SourceMap>(map) + }; Some(Cow::Borrowed(map)) - }, + } None => None, } } else { @@ -122,9 +124,11 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> { // This is based on the following assumptions: // 1. `SourceMap` will be valid for the entire duration of the application. // 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety. - let map = unsafe { std::mem::transmute::<&SourceMap, &'static SourceMap>(map) }; + let map = unsafe { + std::mem::transmute::<&SourceMap, &'static SourceMap>(map) + }; Some(Cow::Borrowed(map)) - }, + } None => None, } } diff --git a/src/source_map_source.rs b/src/source_map_source.rs index 0613e4ec..5f2da52b 100644 --- a/src/source_map_source.rs +++ b/src/source_map_source.rs @@ -427,11 +427,15 @@ mod tests { } test_cached!(source, |s: &dyn Source| s.source().to_string()); - test_cached!(source, |s: &dyn Source| s.map(&MapOptions::default()).map(|m| m.into_owned())); - test_cached!(source, |s: &dyn Source| s.map(&MapOptions { - columns: false, - final_source: true - }).map(|m| m.into_owned())); + test_cached!(source, |s: &dyn Source| s + .map(&MapOptions::default()) + .map(|m| m.into_owned())); + test_cached!(source, |s: &dyn Source| s + .map(&MapOptions { + columns: false, + final_source: true + }) + .map(|m| m.into_owned())); } #[test] @@ -611,9 +615,8 @@ mod tests { }"#, ) .unwrap(); - let inner_source_map = inner_source - .map(&MapOptions::default()) - .map(|map| { + let inner_source_map = + inner_source.map(&MapOptions::default()).map(|map| { let mut map = map.into_owned(); map.set_source_root(Some("/path/to/folder/".to_string())); map