Skip to content

Commit cacda2a

Browse files
committed
perf: more parallel real content hash plugin
1 parent 41f1148 commit cacda2a

File tree

1 file changed

+113
-41
lines changed
  • crates/rspack_plugin_real_content_hash/src

1 file changed

+113
-41
lines changed

crates/rspack_plugin_real_content_hash/src/lib.rs

Lines changed: 113 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -131,49 +131,99 @@ async fn inner_impl(compilation: &mut Compilation) -> Result<()> {
131131
})
132132
.collect();
133133

134-
let ordered_hashes = OrderedHashesBuilder::new(&hash_to_asset_names, &assets_data).build();
134+
let (ordered_hashes, mut hash_dependencies) =
135+
OrderedHashesBuilder::new(&hash_to_asset_names, &assets_data).build();
136+
let mut ordered_hashes_iter = ordered_hashes.iter();
137+
135138
logger.time_end(start);
136139

137140
let start = logger.time("old hash to new hash");
138141
let mut hash_to_new_hash = HashMap::default();
139142

140143
let hooks = RealContentHashPlugin::get_compilation_hooks(compilation.id());
141-
for old_hash in &ordered_hashes {
142-
if let Some(asset_names) = hash_to_asset_names.get_mut(old_hash.as_str()) {
143-
asset_names.sort();
144-
let mut asset_contents: Vec<_> = asset_names
145-
.par_iter()
146-
.filter_map(|name| assets_data.get(name))
147-
.map(|data| {
148-
data
149-
.compute_new_source(
150-
data.own_hashes.contains(old_hash),
151-
&hash_to_new_hash,
152-
&hash_regexp,
153-
)
154-
.clone()
155-
})
156-
.collect();
157-
asset_contents.dedup();
158-
let updated_hash = hooks
159-
.update_hash
160-
.call(compilation, &asset_contents, old_hash)
161-
.await?;
162-
163-
let new_hash = if let Some(new_hash) = updated_hash {
164-
new_hash
165-
} else {
166-
let mut hasher = RspackHash::from(&compilation.options.output);
167-
for asset_content in asset_contents {
168-
hasher.write(&asset_content.buffer());
169-
}
170-
let new_hash = hasher.digest(&compilation.options.output.hash_digest);
171-
let new_hash = new_hash.rendered(old_hash.len()).to_string();
172-
new_hash
144+
145+
let mut computed_hashes = HashSet::default();
146+
let mut top_task = ordered_hashes_iter.next();
147+
148+
loop {
149+
let Some(top) = top_task else {
150+
break;
151+
};
152+
let mut batch = vec![top];
153+
top_task = None;
154+
155+
for hash in ordered_hashes_iter.by_ref() {
156+
let Some(dependencies) = hash_dependencies.remove(hash.as_str()) else {
157+
top_task = Some(hash);
158+
break;
173159
};
174-
hash_to_new_hash.insert(old_hash, new_hash);
160+
if dependencies.iter().all(|dep| computed_hashes.contains(dep)) {
161+
batch.push(hash);
162+
} else {
163+
top_task = Some(hash);
164+
break;
165+
}
166+
}
167+
168+
let batch_source_tasks = batch
169+
.iter()
170+
.filter_map(|hash| {
171+
let assets_names = hash_to_asset_names.get(hash.as_str())?;
172+
let tasks = assets_names
173+
.iter()
174+
.filter_map(|name| {
175+
let data = assets_data.get(name)?;
176+
Some((hash.as_str(), *name, data))
177+
})
178+
.collect::<Vec<_>>();
179+
Some(tasks)
180+
})
181+
.flatten()
182+
.collect::<Vec<_>>();
183+
184+
let mut batch_sources = batch_source_tasks
185+
.into_par_iter()
186+
.map(|(hash, name, data)| {
187+
let new_source = data.compute_new_source(
188+
data.own_hashes.contains(hash),
189+
&hash_to_new_hash,
190+
&hash_regexp,
191+
);
192+
((hash, name), new_source)
193+
})
194+
.collect::<HashMap<_, _>>();
195+
196+
for old_hash in batch.iter() {
197+
if let Some(asset_names) = hash_to_asset_names.get_mut(old_hash.as_str()) {
198+
asset_names.sort();
199+
let mut asset_contents = asset_names
200+
.iter()
201+
.filter_map(|name| batch_sources.remove(&(old_hash.as_str(), name)))
202+
.collect::<Vec<_>>();
203+
asset_contents.dedup();
204+
let updated_hash = hooks
205+
.update_hash
206+
.call(compilation, &asset_contents, old_hash)
207+
.await?;
208+
209+
let new_hash = if let Some(new_hash) = updated_hash {
210+
new_hash
211+
} else {
212+
let mut hasher = RspackHash::from(&compilation.options.output);
213+
for asset_content in asset_contents {
214+
hasher.write(&asset_content.buffer());
215+
}
216+
let new_hash = hasher.digest(&compilation.options.output.hash_digest);
217+
let new_hash = new_hash.rendered(old_hash.len()).to_string();
218+
new_hash
219+
};
220+
hash_to_new_hash.insert(old_hash, new_hash);
221+
}
175222
}
223+
224+
computed_hashes.extend(batch);
176225
}
226+
177227
logger.time_end(start);
178228

179229
let start = logger.time("collect hash updates");
@@ -193,7 +243,7 @@ async fn inner_impl(compilation: &mut Compilation) -> Result<()> {
193243
})
194244
.into_owned();
195245
let new_name = (name != new_name).then_some(new_name);
196-
Some((name.to_owned(), new_source.clone(), new_name))
246+
Some((name.to_owned(), new_source, new_name))
197247
})
198248
.collect();
199249
logger.time_end(start);
@@ -279,7 +329,7 @@ impl AssetData {
279329
without_own: bool,
280330
hash_to_new_hash: &HashMap<&str, String>,
281331
hash_regexp: &Regex,
282-
) -> &BoxSource {
332+
) -> BoxSource {
283333
(if without_own {
284334
&self.new_source_without_own
285335
} else {
@@ -309,6 +359,7 @@ impl AssetData {
309359
}
310360
self.old_source.clone()
311361
})
362+
.clone()
312363
}
313364
}
314365

@@ -328,12 +379,29 @@ impl<'a> OrderedHashesBuilder<'a> {
328379
}
329380
}
330381

331-
pub fn build(&self) -> IndexSet<String> {
382+
pub fn build(&self) -> (IndexSet<String>, HashMap<String, HashSet<String>>) {
332383
let mut ordered_hashes = IndexSet::default();
384+
let mut hash_dependencies = HashMap::default();
333385
for hash in self.hash_to_asset_names.keys() {
334-
self.add_to_ordered_hashes(hash, &mut ordered_hashes, &mut HashSet::default());
386+
self.add_to_ordered_hashes(
387+
hash,
388+
&mut ordered_hashes,
389+
&mut HashSet::default(),
390+
&mut hash_dependencies,
391+
);
335392
}
336-
ordered_hashes
393+
(
394+
ordered_hashes,
395+
hash_dependencies
396+
.into_iter()
397+
.map(|(k, v)| {
398+
(
399+
k.to_string(),
400+
v.into_iter().map(|s| s.to_string()).collect(),
401+
)
402+
})
403+
.collect(),
404+
)
337405
}
338406
}
339407

@@ -364,8 +432,12 @@ impl OrderedHashesBuilder<'_> {
364432
hash: &'b str,
365433
ordered_hashes: &mut IndexSet<String>,
366434
stack: &mut HashSet<&'b str>,
435+
hash_dependencies: &mut HashMap<&'b str, HashSet<&'b str>>,
367436
) {
368-
let deps = self.get_hash_dependencies(hash);
437+
let deps = hash_dependencies
438+
.entry(hash)
439+
.or_insert_with(|| self.get_hash_dependencies(hash))
440+
.clone();
369441
stack.insert(hash);
370442
for dep in deps {
371443
if ordered_hashes.contains(dep) {
@@ -376,7 +448,7 @@ impl OrderedHashesBuilder<'_> {
376448
// so there shouldn't have circular hash dependency between chunks
377449
panic!("RealContentHashPlugin: circular hash dependency");
378450
}
379-
self.add_to_ordered_hashes(dep, ordered_hashes, stack);
451+
self.add_to_ordered_hashes(dep, ordered_hashes, stack, hash_dependencies);
380452
}
381453
ordered_hashes.insert(hash.to_string());
382454
stack.remove(hash);

0 commit comments

Comments
 (0)