Fix perf issue with many safepoints.
In wasmtime's `gc::many_live_refs` unit-test, approximately ~1K vregs are live over ~1K safepoints (actually, each vreg is live over half the safepoints on average, in a LIFO sort of arrangement). This causes a huge slowdown with the current heuristics. Basically, each vreg had a `Conflict` requirement because it had both stack uses (safepoints) and register uses (the actual def and normal use). The action in this case when processing the vreg's bundle is to split off the first use -- a conservative-but-correct approach that will always eventually split bundles far enough to get non-conflicting-requirement pieces. However, because each vreg had N stack uses followed by one register use, this meant that each had to be split N times (!) -- so we had O(n^2) splits and O(n^2) bundles by the end of the allocation. This instead implements another simple heuristic that is much better: when the requirements are conflicting, scan forward and find the exact point at which the requirements become conflicting, such that the prefix (first half prior to the split) still has no conflict, and split there. This turns the above test-case into an O(n)-bundle / O(n)-split situation.
This commit is contained in:
@@ -955,9 +955,11 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
|
||||
if self.func.is_safepoint(inst) {
|
||||
log::debug!("inst{} is safepoint", inst.index());
|
||||
self.safepoints.push(inst);
|
||||
for vreg in live.iter() {
|
||||
if let Some(safepoints) = self.safepoints_per_vreg.get_mut(&vreg) {
|
||||
log::debug!("vreg v{} live at safepoint inst{}", vreg, inst.index());
|
||||
safepoints.insert(inst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +363,29 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_conflict_split_point(&self, bundle: LiveBundleIndex) -> ProgPoint {
|
||||
// Find the first use whose requirement causes the merge up to
|
||||
// this point to go to Conflict.
|
||||
let mut req = Requirement::Unknown;
|
||||
for entry in &self.bundles[bundle.index()].ranges {
|
||||
for u in &self.ranges[entry.index.index()].uses {
|
||||
let this_req = Requirement::from_operand(u.operand);
|
||||
req = req.merge(this_req);
|
||||
if req == Requirement::Conflict {
|
||||
return u.pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: start of bundle.
|
||||
self.bundles[bundle.index()]
|
||||
.ranges
|
||||
.first()
|
||||
.unwrap()
|
||||
.range
|
||||
.from
|
||||
}
|
||||
|
||||
pub fn get_or_create_spill_bundle(
|
||||
&mut self,
|
||||
bundle: LiveBundleIndex,
|
||||
@@ -734,15 +757,17 @@ impl<'a, F: Function> Env<'a, F> {
|
||||
log::debug!("process_bundle: bundle {:?} hint {:?}", bundle, hint_reg,);
|
||||
|
||||
if let Requirement::Conflict = req {
|
||||
// We have to split right away.
|
||||
// We have to split right away. We'll find a point to
|
||||
// split that would allow at least the first half of the
|
||||
// split to be conflict-free.
|
||||
assert!(
|
||||
!self.minimal_bundle(bundle),
|
||||
"Minimal bundle with conflict!"
|
||||
);
|
||||
let bundle_start = self.bundles[bundle.index()].ranges[0].range.from;
|
||||
let split_point = self.find_conflict_split_point(bundle);
|
||||
self.split_and_requeue_bundle(
|
||||
bundle,
|
||||
/* split_at_point = */ bundle_start,
|
||||
/* split_at_point = */ split_point,
|
||||
reg_hint,
|
||||
);
|
||||
return Ok(());
|
||||
|
||||
Reference in New Issue
Block a user