diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 09:25:40 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 09:25:40 -0400 |
| commit | 9a5a1fbdf68859db978b0082f2bd257e6faa60c4 (patch) | |
| tree | 6157b1a63e65045edae016b79fc230f3552cbdfe /src | |
| parent | c7dffce68ad2678c9998045191991e1a6c8b228a (diff) | |
dynarmic: skip untouched locations when an IR instruction ends
Compiling a block is most of what makes Mario Kart World stutter in its
first minute: the game's thread translates ~20,000 blocks in a few
hundred milliseconds when a scene loads, and the frame waits. A CPU
profile of those bursts had ~40% of the compile time in two loops over
all 96 host locations (16 GPRs, 16 XMMs, 64 spill slots) per IR
instruction:
- EndOfAllocScope called ReleaseAll on every location, though an
instruction touches a few. It now skips the ones ReleaseAll would not
change (no references or locks, not scratch, no last use pending, and
values either still live or fully cleared).
- DefineValueImpl checked, with an assert that stays on in release
builds, that the defined value was in no location yet, and afterwards
that it was where it was put. Those scans are DEBUG_ASSERTs now.
Mario Kart World, first 45 s (boot, world map, title, attract demo):
18.5 -> 12.8 us per compiled block; frames over 60 ms 57 -> 31, over
100 ms in the first 600 presents 23 -> 12; frames render as before.
Diffstat (limited to 'src')
| -rw-r--r-- | src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp | 14 | ||||
| -rw-r--r-- | src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h | 9 |
2 files changed, 16 insertions, 7 deletions
diff --git a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp index 5a828839c1..4f3d253441 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.cpp @@ -465,22 +465,24 @@ std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const noex return std::nullopt; } +// The checks that scan every location for def_inst are debug-only: per IR instruction they cost as +// much as the rest of register allocation. void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, HostLoc host_loc) noexcept { - ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined"); + DEBUG_ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined"); LocInfo(host_loc).AddValue(host_loc, def_inst); - ASSERT(*ValueLocation(def_inst) == host_loc); + DEBUG_ASSERT(*ValueLocation(def_inst) == host_loc); } void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, const IR::Value& use_inst) noexcept { - ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined"); + DEBUG_ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined"); if (use_inst.IsImmediate()) { const HostLoc location = ScratchImpl(code, gpr_order); DefineValueImpl(code, def_inst, location); LoadImmediate(code, use_inst, location); } else { - ASSERT(ValueLocation(use_inst.GetInst()) && "use_inst must already be defined"); - const HostLoc location = *ValueLocation(use_inst.GetInst()); - DefineValueImpl(code, def_inst, location); + const auto location = ValueLocation(use_inst.GetInst()); + ASSERT(location && "use_inst must already be defined"); + DefineValueImpl(code, def_inst, *location); } } diff --git a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h index 455c6a970a..0cad07796c 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h +++ b/src/dynarmic/src/dynarmic/backend/x64/reg_alloc.h @@ -65,6 +65,11 @@ public: } void ReleaseOne() noexcept; void ReleaseAll() noexcept; + /// True when ReleaseAll would change nothing, as for most of the spill slots most of the time. + inline bool IsReleased() const noexcept { + return current_references == 0 && is_being_used_count == 0 && !is_scratch && !is_set_last_use && + (total_uses != accumulated_uses || (values.empty() && total_uses == 0 && max_bit_width == 0)); + } constexpr size_t GetMaxBitWidth() const noexcept { return 1 << max_bit_width; } void AddValue(HostLoc loc, IR::Inst* inst) noexcept; /// Checks if the given instruction is in our values set @@ -201,8 +206,10 @@ public: void ReleaseStackSpace(BlockOfCode& code, const size_t stack_space) noexcept; inline void EndOfAllocScope() noexcept { + // After every IR instruction: skip the locations it left alone. for (auto& iter : hostloc_info) - iter.ReleaseAll(); + if (!iter.IsReleased()) + iter.ReleaseAll(); } inline void AssertNoMoreUses() noexcept { ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), [](const auto& i) noexcept { return i.IsEmpty(); })); |
