mirror of
https://github.com/jessicanataliagta/PSPRecomp
synced 2026-09-26 16:49:34 -04:00
OTIMIZAÇOES ROUND 2
OTIMIZAÇOES ROUND 2
This commit is contained in:
@@ -65,6 +65,18 @@ void set_runtime_thread_identity(std::int32_t uid, const std::string &name) noex
|
||||
[[nodiscard]] std::uint64_t runtime_thread_switch_generation() noexcept;
|
||||
[[nodiscard]] bool runtime_thread_switch_generation_matches(std::uint64_t generation) noexcept;
|
||||
|
||||
// Per-generated-unit call census, armed with PSPRECOMP_UNIT_PROFILE=1.
|
||||
//
|
||||
// Exists to answer one question the build cannot answer on its own: which of the
|
||||
// 234 generated units are actually hot, so VCS_HOT_UNIT_IDS can name them
|
||||
// instead of carrying a single hand-picked entry. Counting is a load of one
|
||||
// global bool and a predictable branch on the chained-call path, so leaving it
|
||||
// compiled in costs nothing measurable when it is off.
|
||||
inline constexpr std::size_t kUnitProfileCapacity = 512u;
|
||||
extern bool g_unit_profile_enabled;
|
||||
extern std::uint64_t g_unit_profile_counts[kUnitProfileCapacity];
|
||||
void report_unit_profile(std::size_t limit = 40u);
|
||||
|
||||
class Runtime {
|
||||
public:
|
||||
using RecompiledFunction = void (*)(Runtime &, AllegrexContext &);
|
||||
@@ -216,6 +228,7 @@ public:
|
||||
} else {
|
||||
Function(*this, ctx);
|
||||
}
|
||||
if (g_unit_profile_enabled) ++g_unit_profile_counts[UnitIndex];
|
||||
#if !defined(PSPRECOMP_AOT_PRODUCTION_FASTPATHS)
|
||||
if (track_dispatch_counters_) {
|
||||
++chained_dispatches_;
|
||||
|
||||
+34
-14
@@ -20,15 +20,23 @@ endif()
|
||||
# Full /Ob3 is valuable at runtime but several large, cold units still make
|
||||
# MSVC spend minutes and close to a gigabyte per compiler process. Keep those
|
||||
# units buildable, then override this level only for the measured hot corpus.
|
||||
# /Ob0 does not merely decline to auto-inline: MSVC drops __forceinline with it
|
||||
# too, verified by compiling a stand-in accessor at each level and reading the
|
||||
# disassembly -- /Ob0 emits a call to the accessor itself, /Ob1 and above emit a
|
||||
# call only to its cold fallback. Every guest load and store in the recompiled
|
||||
# corpus goes through a __forceinline fast path, so /Ob0 turned the single most
|
||||
# frequent operation in the program into an out-of-line call. /Ob1 restores it
|
||||
# while still refusing the aggressive auto-inlining that made /Ob3 blow up the
|
||||
# optimizer on the larger units.
|
||||
set(PSPRECOMP_GENERATED_INLINE_LEVEL "1" CACHE STRING
|
||||
# Keep this at 0, and do not "fix" it without measuring.
|
||||
#
|
||||
# /Ob0 does drop __forceinline, not merely auto-inlining: compiling a stand-in
|
||||
# accessor at each level and reading the disassembly shows /Ob0 emitting a call
|
||||
# to the accessor itself while /Ob1+ emit a call only to its cold fallback. Since
|
||||
# every guest load and store goes through a __forceinline fast path, /Ob0 looks
|
||||
# like it must be turning the most frequent operation in the program into an
|
||||
# out-of-line call, and /Ob1 looks like a free win.
|
||||
#
|
||||
# It measures 13.6% slower. On a 3800-frame route, median frame 11.84 ms at /Ob0
|
||||
# against 13.69 ms at /Ob1 (median FPS 84.5 against 73.0), guest cpu_us 9.66 ms
|
||||
# against 11.54 ms. Inlining the fast path at every call site grows the binary
|
||||
# from 103 MB to 232 MB, and this corpus is already hostile to the instruction
|
||||
# cache: 234 units, ~50k cross-unit chains, over a million architectural register
|
||||
# references. The call overhead removed is smaller than the I-cache and BTB
|
||||
# pressure added.
|
||||
set(PSPRECOMP_GENERATED_INLINE_LEVEL "0" CACHE STRING
|
||||
"MSVC /Ob level for generated VCS AOT units (0, 1, 2 or 3)")
|
||||
set_property(CACHE PSPRECOMP_GENERATED_INLINE_LEVEL PROPERTY STRINGS 0 1 2 3)
|
||||
if(NOT PSPRECOMP_GENERATED_INLINE_LEVEL MATCHES "^[0-3]$")
|
||||
@@ -66,12 +74,24 @@ else()
|
||||
endif()
|
||||
|
||||
if(PSPRECOMP_PROFILE_GUIDED_AOT)
|
||||
# This unit is hot in the current capture and finishes /Ob3 without
|
||||
# triggering MSVC's pathological optimizer growth. It also contains the
|
||||
# validated native collision leaf at guest PC 0x088B1554. Other measured
|
||||
# units remain /Ob0 until their oversized helpers are split/noinline.
|
||||
# The units that carry 80% of the guest's cross-unit calls, measured with
|
||||
# PSPRECOMP_UNIT_PROFILE=1 over a 7900-frame route: 141 million calls across
|
||||
# 201 live units, roughly 17,900 unit transitions per frame.
|
||||
#
|
||||
# Only 0043 used to be here, and the census puts it eighth at 3.3% -- the
|
||||
# aggressive settings were reaching 3% of the work. 0023 and 0024 alone are
|
||||
# 16.6%.
|
||||
#
|
||||
# Why a list and not the whole corpus: /Ob1 applied to all 234 units measured
|
||||
# 13.6% *slower* (median frame 13.69 ms against 11.84 ms) because the binary
|
||||
# grew from 103 MB to 232 MB and this corpus is already hostile to the
|
||||
# instruction cache. Confining the aggressive settings to the hot 16% keeps
|
||||
# the code growth bounded where it cannot pay for itself.
|
||||
set(VCS_HOT_UNIT_IDS
|
||||
0043)
|
||||
0023 0024 0097 0030 0011 0091 0084 0043 0067 0163
|
||||
0087 0142 0066 0152 0109 0179 0085 0215 0064 0149
|
||||
0095 0199 0092 0206 0022 0190 0200 0080 0164 0198
|
||||
0088 0155 0213 0154 0169 0035 0089)
|
||||
set(VCS_HOT_SOURCES)
|
||||
foreach(VCS_UNIT_ID IN LISTS VCS_HOT_UNIT_IDS)
|
||||
set(VCS_UNIT_SOURCE "${VCS_PROFILE_DIR}/generated/generated_unit_${VCS_UNIT_ID}.cpp")
|
||||
|
||||
@@ -307,6 +307,7 @@ int main(int argc, char **argv) {
|
||||
psprecomp::report_counted_pcs();
|
||||
if (shutdown_diag) std::cerr << "[shutdown] counted-pcs-reported\n";
|
||||
runtime.report_hle_histogram();
|
||||
psprecomp::report_unit_profile();
|
||||
if (shutdown_diag) std::cerr << "[shutdown] hle-histogram-reported\n";
|
||||
vcs::report_disc_read_stats();
|
||||
vcs::report_present_stats();
|
||||
|
||||
@@ -122,7 +122,7 @@ echo [1/7] Configuring persistent Ninja Release tree...
|
||||
-DPSPRECOMP_MSVC_MP_JOBS=1 ^
|
||||
-DPSPRECOMP_PROFILE_GUIDED_AOT=ON ^
|
||||
-DPSPRECOMP_HOT_GENERATED_OPT_LEVEL=3 ^
|
||||
-DPSPRECOMP_GENERATED_INLINE_LEVEL=1 ^
|
||||
-DPSPRECOMP_GENERATED_INLINE_LEVEL=0 ^
|
||||
-DPSPRECOMP_HOT_GENERATED_INLINE_LEVEL=3 ^
|
||||
-DPSPRECOMP_VCS_AOT_LTO=OFF ^
|
||||
-DPSPRECOMP_BUILD_TESTS=ON ^
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
@@ -140,6 +141,7 @@ Runtime::Runtime(std::uint32_t ram_size) : memory_(ram_size) {
|
||||
// in the middle of guest execution; larger profiles can still grow it.
|
||||
import_bindings_.resize(256u, nullptr);
|
||||
hle_histogram_enabled_ = std::getenv("PSPRECOMP_HLE_HISTOGRAM") != nullptr;
|
||||
g_unit_profile_enabled = std::getenv("PSPRECOMP_UNIT_PROFILE") != nullptr;
|
||||
#if defined(PSPRECOMP_AOT_PRODUCTION_FASTPATHS)
|
||||
track_dispatch_counters_ = false;
|
||||
#else
|
||||
@@ -304,6 +306,7 @@ bool Runtime::invoke_chained_unit(AllegrexContext &ctx, std::uint32_t unit_index
|
||||
if (unit_index >= kGeneratedUnitFastCapacity || !generated_unit_layout_valid_) return false;
|
||||
RecompiledFunction function = generated_units_[unit_index];
|
||||
if (function == nullptr) return false;
|
||||
if (g_unit_profile_enabled) ++g_unit_profile_counts[unit_index];
|
||||
|
||||
const std::uint32_t target_pc = ctx.pc;
|
||||
const std::uint32_t native_depth = chain_depth_;
|
||||
@@ -384,6 +387,33 @@ std::vector<std::pair<std::string, std::uint64_t>> Runtime::hle_histogram() cons
|
||||
return entries;
|
||||
}
|
||||
|
||||
bool g_unit_profile_enabled = false;
|
||||
std::uint64_t g_unit_profile_counts[kUnitProfileCapacity]{};
|
||||
|
||||
void report_unit_profile(std::size_t limit) {
|
||||
if (!g_unit_profile_enabled) return;
|
||||
std::vector<std::pair<std::uint64_t, std::size_t>> entries;
|
||||
std::uint64_t total = 0u;
|
||||
for (std::size_t index = 0; index < kUnitProfileCapacity; ++index) {
|
||||
if (g_unit_profile_counts[index] == 0u) continue;
|
||||
entries.emplace_back(g_unit_profile_counts[index], index);
|
||||
total += g_unit_profile_counts[index];
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), std::greater<>());
|
||||
std::cerr << "[unit-profile] units=" << entries.size() << " calls=" << total << "\n";
|
||||
std::uint64_t running = 0u;
|
||||
for (std::size_t i = 0; i < std::min(limit, entries.size()); ++i) {
|
||||
running += entries[i].first;
|
||||
std::cerr << "[unit-profile] unit=" << std::setw(4) << std::setfill('0')
|
||||
<< entries[i].second << std::setfill(' ')
|
||||
<< " calls=" << entries[i].first
|
||||
<< " share=" << (100.0 * static_cast<double>(entries[i].first) /
|
||||
static_cast<double>(total ? total : 1u))
|
||||
<< "% cumulative=" << (100.0 * static_cast<double>(running) /
|
||||
static_cast<double>(total ? total : 1u)) << "%\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Runtime::report_hle_histogram(std::size_t limit) const {
|
||||
if (!hle_histogram_enabled_) return;
|
||||
const auto entries = hle_histogram();
|
||||
|
||||
Reference in New Issue
Block a user