Skip to content

Commit 95b9225

Browse files
authored
perf: introduce ModuleGraphCache and cache the result of get_mode and determine_export_assignments (#10584)
* Remove useless parameters * Add ModuleGraphCacheArtifact * Cache FlagDependencyExportsPlugin and seal * Cache determine_export_assignments * Reduce DetermineExportAssignmentsKey size * Reduce GetModeCacheKey size * Unfreeze in rebuild_module * Freeze and unfreeze in finish * Remove todo * Add comments * Split modules * Update DetermineExportAssignmentsKey * Use fxhashmap * Fix rebase * Fix clippy * Remove module name * Mutex -> RwLock * Update memory orderings
1 parent 7cad404 commit 95b9225

File tree

55 files changed

+738
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+738
-274
lines changed

crates/rspack_core/src/artifacts/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ mod cgm_hash_artifact;
77
mod cgm_runtime_requirement_artifact;
88
mod chunk_hashes_artifact;
99
mod code_generation_results;
10+
mod module_graph_cache_artifact;
1011
mod side_effects_do_optimize_artifact;
1112

1213
pub use cgm_hash_artifact::*;
1314
pub use cgm_runtime_requirement_artifact::*;
1415
pub use chunk_hashes_artifact::*;
1516
pub use code_generation_results::*;
17+
pub use module_graph_cache_artifact::*;
1618
pub use side_effects_do_optimize_artifact::*;
1719

1820
pub type AsyncModulesArtifact = IdentifierSet;
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use std::sync::{
2+
atomic::{AtomicBool, Ordering},
3+
Arc, RwLock,
4+
};
5+
6+
pub use determine_export_assignments::DetermineExportAssignmentsKey;
7+
use determine_export_assignments::*;
8+
use get_mode::*;
9+
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
10+
use swc_core::atoms::Atom;
11+
12+
use crate::{DependencyId, ExportInfo, RuntimeKey};
13+
pub type ModuleGraphCacheArtifact = Arc<ModuleGraphCacheArtifactInner>;
14+
15+
/// This is a rust port of `ModuleGraph.cached` and `ModuleGraph.dependencyCacheProvide` in webpack.
16+
/// We use this to cache the result of functions with high computational overhead.
17+
#[derive(Debug, Default)]
18+
pub struct ModuleGraphCacheArtifactInner {
19+
/// Webpack enables module graph caches by creating new cache maps and disable them by setting them to undefined.
20+
/// But in rust I think it's better to use a bool flag to avoid memory reallocation.
21+
freezed: AtomicBool,
22+
get_mode_cache: GetModeCache,
23+
determine_export_assignments_cache: DetermineExportAssignmentsCache,
24+
}
25+
26+
impl ModuleGraphCacheArtifactInner {
27+
pub fn freeze(&self) {
28+
self.get_mode_cache.freeze();
29+
self.determine_export_assignments_cache.freeze();
30+
self.freezed.store(true, Ordering::Release);
31+
}
32+
33+
pub fn unfreeze(&self) {
34+
self.freezed.store(false, Ordering::Release);
35+
}
36+
37+
pub fn cached_get_mode<F: FnOnce() -> ExportMode>(
38+
&self,
39+
key: GetModeCacheKey,
40+
f: F,
41+
) -> ExportMode {
42+
if !self.freezed.load(Ordering::Acquire) {
43+
return f();
44+
}
45+
46+
match self.get_mode_cache.get(&key) {
47+
Some(value) => value,
48+
None => {
49+
let value = f();
50+
self.get_mode_cache.set(key, value.clone());
51+
value
52+
}
53+
}
54+
}
55+
56+
pub fn cached_determine_export_assignments<F: FnOnce() -> DetermineExportAssignmentsValue>(
57+
&self,
58+
key: DetermineExportAssignmentsKey,
59+
f: F,
60+
) -> DetermineExportAssignmentsValue {
61+
if !self.freezed.load(Ordering::Acquire) {
62+
return f();
63+
}
64+
65+
match self.determine_export_assignments_cache.get(&key) {
66+
Some(value) => value,
67+
None => {
68+
let value = f();
69+
self
70+
.determine_export_assignments_cache
71+
.set(key, value.clone());
72+
value
73+
}
74+
}
75+
}
76+
}
77+
78+
pub(super) mod get_mode {
79+
use super::*;
80+
81+
pub type GetModeCacheKey = (DependencyId, Option<RuntimeKey>);
82+
83+
#[derive(Debug, Default)]
84+
pub struct GetModeCache {
85+
cache: RwLock<HashMap<GetModeCacheKey, ExportMode>>,
86+
}
87+
88+
impl GetModeCache {
89+
pub fn freeze(&self) {
90+
self.cache.write().expect("should get lock").clear();
91+
}
92+
93+
pub fn get(&self, key: &GetModeCacheKey) -> Option<ExportMode> {
94+
let inner = self.cache.read().expect("should get lock");
95+
inner.get(key).cloned()
96+
}
97+
98+
pub fn set(&self, key: GetModeCacheKey, value: ExportMode) {
99+
self
100+
.cache
101+
.write()
102+
.expect("should get lock")
103+
.insert(key, value);
104+
}
105+
}
106+
}
107+
108+
pub(super) mod determine_export_assignments {
109+
use super::*;
110+
use crate::ModuleIdentifier;
111+
112+
/// Webpack cache the result of `determineExportAssignments` with the keys of dependencies arraris of `allStarExports.dependencies` and `otherStarExports` + `this`(DependencyId).
113+
/// See: https://github.com/webpack/webpack/blob/19ca74127f7668aaf60d59f4af8fcaee7924541a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js#L645
114+
///
115+
/// However, we can simplify the cache key since dependencies under the same parent module share `allStarExports` and copy their own `otherStarExports`.
116+
#[derive(Debug, PartialEq, Eq, Hash)]
117+
pub enum DetermineExportAssignmentsKey {
118+
All(ModuleIdentifier),
119+
Other(DependencyId),
120+
}
121+
pub type DetermineExportAssignmentsValue = (Vec<Atom>, Vec<usize>);
122+
123+
#[derive(Debug, Default)]
124+
pub struct DetermineExportAssignmentsCache {
125+
cache: RwLock<HashMap<DetermineExportAssignmentsKey, DetermineExportAssignmentsValue>>,
126+
}
127+
128+
impl DetermineExportAssignmentsCache {
129+
pub fn freeze(&self) {
130+
self.cache.write().expect("should get lock").clear();
131+
}
132+
133+
pub fn get(
134+
&self,
135+
key: &DetermineExportAssignmentsKey,
136+
) -> Option<DetermineExportAssignmentsValue> {
137+
let inner = self.cache.read().expect("should get lock");
138+
inner.get(key).cloned()
139+
}
140+
141+
pub fn set(&self, key: DetermineExportAssignmentsKey, value: DetermineExportAssignmentsValue) {
142+
self
143+
.cache
144+
.write()
145+
.expect("should get lock")
146+
.insert(key, value);
147+
}
148+
}
149+
}
150+
151+
#[derive(Debug, Clone)]
152+
pub struct NormalReexportItem {
153+
pub name: Atom,
154+
pub ids: Vec<Atom>,
155+
pub hidden: bool,
156+
pub checked: bool,
157+
pub export_info: ExportInfo,
158+
}
159+
160+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
161+
pub enum ExportModeType {
162+
Missing,
163+
Unused,
164+
EmptyStar,
165+
ReexportDynamicDefault,
166+
ReexportNamedDefault,
167+
ReexportNamespaceObject,
168+
ReexportFakeNamespaceObject,
169+
ReexportUndefined,
170+
NormalReexport,
171+
DynamicReexport,
172+
}
173+
174+
#[derive(Debug, Clone)]
175+
pub struct ExportMode {
176+
/// corresponding to `type` field in webpack's `EpxortMode`
177+
pub ty: ExportModeType,
178+
pub items: Option<Vec<NormalReexportItem>>,
179+
pub name: Option<Atom>,
180+
pub fake_type: u8,
181+
pub partial_namespace_export_info: Option<ExportInfo>,
182+
pub ignored: Option<HashSet<Atom>>,
183+
pub hidden: Option<HashSet<Atom>>,
184+
}
185+
186+
impl ExportMode {
187+
pub fn new(ty: ExportModeType) -> Self {
188+
Self {
189+
ty,
190+
items: None,
191+
name: None,
192+
fake_type: 0,
193+
partial_namespace_export_info: None,
194+
ignored: None,
195+
hidden: None,
196+
}
197+
}
198+
}
199+
200+
#[derive(Debug, Default)]
201+
pub struct StarReexportsInfo {
202+
pub exports: Option<HashSet<Atom>>,
203+
pub checked: Option<HashSet<Atom>>,
204+
pub ignored_exports: HashSet<Atom>,
205+
pub hidden: Option<HashSet<Atom>>,
206+
}

crates/rspack_core/src/build_chunk_graph/code_splitter.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use crate::{
2424
merge_runtime, AsyncDependenciesBlockIdentifier, ChunkGroup, ChunkGroupKind, ChunkGroupOptions,
2525
ChunkGroupUkey, ChunkLoading, ChunkUkey, Compilation, ConnectionState, DependenciesBlock,
2626
DependencyId, DependencyLocation, EntryDependency, EntryRuntime, GroupOptions, Logger,
27-
ModuleDependency, ModuleGraph, ModuleIdentifier, RuntimeSpec, SyntheticDependencyLocation,
27+
ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, ModuleIdentifier, RuntimeSpec,
28+
SyntheticDependencyLocation,
2829
};
2930

3031
type IndexMap<K, V, H = FxHasher> = RawIndexMap<K, V, BuildHasherDefault<H>>;
@@ -310,21 +311,22 @@ fn get_active_state_of_connections(
310311
connections: &[DependencyId],
311312
runtime: Option<&RuntimeSpec>,
312313
module_graph: &ModuleGraph,
314+
module_graph_cache: &ModuleGraphCacheArtifact,
313315
) -> ConnectionState {
314316
let mut iter = connections.iter();
315317
let id = iter.next().expect("should have connection");
316318
let mut merged = module_graph
317319
.connection_by_dependency_id(id)
318320
.expect("should have connection")
319-
.active_state(module_graph, runtime);
321+
.active_state(module_graph, runtime, module_graph_cache);
320322
if merged.is_true() {
321323
return merged;
322324
}
323325
for c in iter {
324326
let c = module_graph
325327
.connection_by_dependency_id(c)
326328
.expect("should have connection");
327-
merged = merged + c.active_state(module_graph, runtime);
329+
merged = merged + c.active_state(module_graph, runtime, module_graph_cache);
328330
if merged.is_true() {
329331
return merged;
330332
}
@@ -1648,8 +1650,12 @@ Or do you want to use the entrypoints '{name}' and '{runtime}' independently on
16481650
let modules = map
16491651
.get_mut(&block_id)
16501652
.expect("should have modules in block_modules_runtime_map");
1651-
let active_state =
1652-
get_active_state_of_connections(&connections, runtime, &compilation.get_module_graph());
1653+
let active_state = get_active_state_of_connections(
1654+
&connections,
1655+
runtime,
1656+
&compilation.get_module_graph(),
1657+
&compilation.module_graph_cache_artifact,
1658+
);
16531659
modules.push((module_identifier, active_state, connections));
16541660
}
16551661
}
@@ -1771,6 +1777,7 @@ Or do you want to use the entrypoints '{name}' and '{runtime}' independently on
17711777
connections,
17721778
Some(&cgi.runtime),
17731779
&compilation.get_module_graph(),
1780+
&compilation.module_graph_cache_artifact,
17741781
);
17751782
if active_state.is_false() {
17761783
continue;

crates/rspack_core/src/build_chunk_graph/new_code_splitter.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use crate::{
1818
merge_runtime, AsyncDependenciesBlockIdentifier, Chunk, ChunkGroup, ChunkGroupKind,
1919
ChunkGroupOptions, ChunkGroupUkey, ChunkLoading, ChunkUkey, Compilation, DependenciesBlock,
2020
DependencyLocation, EntryData, EntryDependency, EntryOptions, EntryRuntime, GroupOptions,
21-
ModuleDependency, ModuleGraph, ModuleGraphConnection, ModuleIdentifier, RuntimeSpec,
22-
SyntheticDependencyLocation,
21+
ModuleDependency, ModuleGraph, ModuleGraphCacheArtifact, ModuleGraphConnection, ModuleIdentifier,
22+
RuntimeSpec, SyntheticDependencyLocation,
2323
};
2424

2525
type ModuleDeps = HashMap<
@@ -197,6 +197,7 @@ impl CreateChunkRoot {
197197

198198
fn create(&self, splitter: &CodeSplitter, compilation: &Compilation) -> Vec<ChunkDesc> {
199199
let module_graph = compilation.get_module_graph();
200+
let module_graph_cache = &compilation.module_graph_cache_artifact;
200201

201202
match self {
202203
CreateChunkRoot::Entry(entry, data, runtime) => {
@@ -237,7 +238,7 @@ impl CreateChunkRoot {
237238
)
238239
.filter_map(|dep_id| module_graph.module_identifier_by_dependency_id(dep_id))
239240
{
240-
splitter.fill_chunk_modules(*m, runtime, &module_graph, &mut ctx);
241+
splitter.fill_chunk_modules(*m, runtime, &module_graph, module_graph_cache, &mut ctx);
241242
}
242243

243244
vec![ChunkDesc::Entry(Box::new(EntryChunkDesc {
@@ -291,7 +292,7 @@ impl CreateChunkRoot {
291292
continue;
292293
};
293294

294-
splitter.fill_chunk_modules(*m, runtime, &module_graph, &mut ctx);
295+
splitter.fill_chunk_modules(*m, runtime, &module_graph, module_graph_cache, &mut ctx);
295296
}
296297

297298
if let Some(group_option) = block.get_group_options()
@@ -451,6 +452,7 @@ impl CodeSplitter {
451452
let mut stack = vec![];
452453
let mut visited = HashSet::default();
453454
let module_graph = compilation.get_module_graph();
455+
let module_graph_cache = &compilation.module_graph_cache_artifact;
454456
let global_chunk_loading = &compilation.options.output.chunk_loading;
455457
let mut roots = HashMap::<AsyncDependenciesBlockIdentifier, CreateChunkRoot>::default();
456458
let mut named_roots = HashMap::<String, CreateChunkRoot>::default();
@@ -501,7 +503,8 @@ impl CodeSplitter {
501503
continue;
502504
}
503505

504-
let guard = self.outgoings_modules(&module, runtime.as_ref(), &module_graph);
506+
let guard =
507+
self.outgoings_modules(&module, runtime.as_ref(), &module_graph, module_graph_cache);
505508
let (modules, blocks) = guard.value();
506509
let blocks = blocks.clone();
507510
for m in modules {
@@ -749,6 +752,7 @@ impl CodeSplitter {
749752
module: &ModuleIdentifier,
750753
runtime: &RuntimeSpec,
751754
module_graph: &ModuleGraph,
755+
module_graph_cache: &ModuleGraphCacheArtifact,
752756
) -> Ref<ModuleIdentifier, (Vec<ModuleIdentifier>, Vec<AsyncDependenciesBlockIdentifier>)> {
753757
let module_map = self.module_deps.get(runtime).expect("should have value");
754758

@@ -783,14 +787,14 @@ impl CodeSplitter {
783787

784788
'outer: for (m, conns) in outgoings.iter() {
785789
for conn in conns {
786-
let conn_state = conn.active_state(module_graph, Some(runtime));
790+
let conn_state = conn.active_state(module_graph, Some(runtime), module_graph_cache);
787791
match conn_state {
788792
crate::ConnectionState::Active(true) => {
789793
modules.insert(*m);
790794
continue 'outer;
791795
}
792796
crate::ConnectionState::TransitiveOnly => {
793-
let transitive = self.outgoings_modules(m, runtime, module_graph);
797+
let transitive = self.outgoings_modules(m, runtime, module_graph, module_graph_cache);
794798
let (extra_modules, extra_blocks) = transitive.value();
795799
modules.extend(extra_modules.iter().copied());
796800
blocks.extend(extra_blocks.iter().copied());
@@ -811,6 +815,7 @@ impl CodeSplitter {
811815
target_module: ModuleIdentifier,
812816
runtime: &RuntimeSpec,
813817
module_graph: &ModuleGraph,
818+
module_graph_cache: &ModuleGraphCacheArtifact,
814819
ctx: &mut FillCtx,
815820
) {
816821
enum Task {
@@ -855,7 +860,8 @@ impl CodeSplitter {
855860
value
856861
});
857862

858-
let guard = self.outgoings_modules(&target_module, runtime, module_graph);
863+
let guard =
864+
self.outgoings_modules(&target_module, runtime, module_graph, module_graph_cache);
859865
let (_, (outgoing_modules, blocks)) = guard.pair();
860866
let mut outgoing_modules = outgoing_modules.clone();
861867

@@ -940,6 +946,7 @@ impl CodeSplitter {
940946

941947
let mut visited = HashSet::default();
942948
let module_graph = compilation.get_module_graph();
949+
let module_graph_cache = &compilation.module_graph_cache_artifact;
943950

944951
queue.reverse();
945952

@@ -983,7 +990,7 @@ impl CodeSplitter {
983990
if !self.module_deps.contains_key(runtime) {
984991
self.module_deps.insert(runtime.clone(), Default::default());
985992
}
986-
let guard = self.outgoings_modules(&m, runtime, &module_graph);
993+
let guard = self.outgoings_modules(&m, runtime, &module_graph, module_graph_cache);
987994
let (modules, blocks) = guard.value();
988995

989996
for m in modules.iter().rev() {

0 commit comments

Comments
 (0)