OTIMIZAÇOES ROUND 2

OTIMIZAÇOES ROUND 2
This commit is contained in:
Jessica_Natalia
2026-08-16 01:28:40 -03:00
parent 818261a985
commit 92fb08719e
5 changed files with 79 additions and 15 deletions
+13
View File
@@ -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
View File
@@ -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")
+1
View File
@@ -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();
+1 -1
View File
@@ -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 ^
+30
View File
@@ -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();