mais otimizaçoes

mais otimizaçoes
This commit is contained in:
Jessica_Natalia
2026-08-13 11:20:36 -03:00
parent 019d431190
commit 98d6b05477
9 changed files with 77 additions and 50 deletions
+10 -2
View File
@@ -36,12 +36,16 @@ endif()
# unbounded product exhausts physical memory. Cap the per-cl.exe degree here and
# let the build scripts pick a memory-aware value.
set(PSPRECOMP_MSVC_MP_JOBS "0" CACHE STRING
"Max cl.exe front-ends per /MP invocation (0 = one per logical core)")
"Max cl.exe front-ends (0 = logical-core default, 1 = disable /MP)")
if(NOT PSPRECOMP_MSVC_MP_JOBS MATCHES "^[0-9]+$")
message(FATAL_ERROR "PSPRECOMP_MSVC_MP_JOBS must be a non-negative integer")
endif()
if(PSPRECOMP_MSVC_MP_JOBS STREQUAL "0")
set(PSPRECOMP_MSVC_MP_FLAG "/MP")
elseif(PSPRECOMP_MSVC_MP_JOBS STREQUAL "1")
# Omitting /MP is materially different from /MP1: the latter still creates
# and communicates with a child compiler process and can fail with D8040.
set(PSPRECOMP_MSVC_MP_FLAG "")
else()
set(PSPRECOMP_MSVC_MP_FLAG "/MP${PSPRECOMP_MSVC_MP_JOBS}")
endif()
@@ -99,7 +103,11 @@ add_executable(dump_function tools/dump_function.cpp)
target_link_libraries(dump_function PRIVATE psprecomp_core)
function(psprecomp_enable_host_avx target)
if(PSPRECOMP_HOST_AVX AND MSVC)
# /arch:AVX appears after the directory-wide /arch:AVX2 option in MSVC's
# command line and therefore silently downgrades the whole target. AVX2
# already includes the AVX scalar instructions this compatibility switch
# was introduced for, so do not emit the weaker override in an AVX2 build.
if(PSPRECOMP_HOST_AVX AND MSVC AND NOT PSPRECOMP_NATIVE_AVX2)
target_compile_options(${target} PRIVATE /arch:AVX)
endif()
endfunction()
+6 -6
View File
@@ -126,7 +126,7 @@ public:
: owner_(owner), ram_data_(ram_data), ram_limit8_(limit8),
ram_limit16_(limit16), ram_limit32_(limit32),
write_watch_enabled_(write_watch) {}
[[nodiscard]] static constexpr std::uint32_t ram_offset_of_fast(
[[nodiscard]] PSPRECOMP_MEMORY_FAST_PATH static constexpr std::uint32_t ram_offset_of_fast(
std::uint32_t address) noexcept {
return (address & 0x1FFFFFFFu) - GuestMemory::kPhysicalBase;
}
@@ -261,20 +261,20 @@ private:
// Canonicalize and rebase in one step. An address below kPhysicalBase --
// EDRAM included -- wraps to a value far above any RAM size, so a single
// unsigned compare rejects it along with every out-of-range access.
[[nodiscard]] static std::uint32_t ram_offset_of(std::uint32_t address) noexcept {
[[nodiscard]] PSPRECOMP_MEMORY_FAST_PATH static std::uint32_t ram_offset_of(std::uint32_t address) noexcept {
return (address & 0x1FFFFFFFu) - kPhysicalBase;
}
// Guest memory is little-endian, so on a little-endian host these are the
// same bytes the previous per-byte assembly produced, in one access.
[[nodiscard]] static std::uint16_t read_le16(const std::uint8_t *source) noexcept {
[[nodiscard]] PSPRECOMP_MEMORY_FAST_PATH static std::uint16_t read_le16(const std::uint8_t *source) noexcept {
std::uint16_t value{};
std::memcpy(&value, source, sizeof(value));
if constexpr (std::endian::native == std::endian::big)
value = static_cast<std::uint16_t>((value >> 8u) | (value << 8u));
return value;
}
[[nodiscard]] static std::uint32_t read_le32(const std::uint8_t *source) noexcept {
[[nodiscard]] PSPRECOMP_MEMORY_FAST_PATH static std::uint32_t read_le32(const std::uint8_t *source) noexcept {
std::uint32_t value{};
std::memcpy(&value, source, sizeof(value));
if constexpr (std::endian::native == std::endian::big)
@@ -282,12 +282,12 @@ private:
((value << 8u) & 0x00FF0000u) | ((value << 24u) & 0xFF000000u);
return value;
}
static void write_le16(std::uint8_t *destination, std::uint16_t value) noexcept {
PSPRECOMP_MEMORY_FAST_PATH static void write_le16(std::uint8_t *destination, std::uint16_t value) noexcept {
if constexpr (std::endian::native == std::endian::big)
value = static_cast<std::uint16_t>((value >> 8u) | (value << 8u));
std::memcpy(destination, &value, sizeof(value));
}
static void write_le32(std::uint8_t *destination, std::uint32_t value) noexcept {
PSPRECOMP_MEMORY_FAST_PATH static void write_le32(std::uint8_t *destination, std::uint32_t value) noexcept {
if constexpr (std::endian::native == std::endian::big)
value = ((value >> 24u) & 0x000000FFu) | ((value >> 8u) & 0x0000FF00u) |
((value << 8u) & 0x00FF0000u) | ((value << 24u) & 0xFF000000u);
+1 -1
View File
@@ -71,7 +71,7 @@ public:
using RecompiledEntryFunction = void (*)(Runtime &, AllegrexContext &, std::uint16_t,
GuestMemory::AotFastView &);
using HleFunction = std::function<void(Runtime &, AllegrexContext &)>;
using NativeFastPath = std::function<void(Runtime &, AllegrexContext &)>;
using NativeFastPath = void (*)(Runtime &, AllegrexContext &);
explicit Runtime(std::uint32_t ram_size = 32u * 1024u * 1024u);
+37 -17
View File
@@ -9,7 +9,7 @@ option(PSPRECOMP_VCS_AOT_LTO
"Include generated VCS AOT units in MSVC whole-program optimization" OFF)
option(PSPRECOMP_PROFILE_GUIDED_AOT
"Optimize the measured VCS hot AOT units above the base generated level" OFF)
"Optimize the measured VCS hot AOT units above the base generated level" ON)
set(PSPRECOMP_HOT_GENERATED_OPT_LEVEL "2" CACHE STRING
"Optimization level for profile-guided VCS AOT units (0, 1, 2 or 3)")
set_property(CACHE PSPRECOMP_HOT_GENERATED_OPT_LEVEL PROPERTY STRINGS 0 1 2 3)
@@ -17,24 +17,21 @@ if(NOT PSPRECOMP_HOT_GENERATED_OPT_LEVEL MATCHES "^[0-3]$")
message(FATAL_ERROR "PSPRECOMP_HOT_GENERATED_OPT_LEVEL must be 0, 1, 2 or 3")
endif()
# Inline expansion is what makes MSVC pathological on parts of this corpus.
# Measured on generated_unit_0000/0002/0004/0005: /Ob3 and /Ob1 never finish (>90 s,
# 0.6-2.5 GB each and still climbing), while /Ob0 compiles the same unit in ~10 s
# at 0.18 GB. Healthy units are unaffected by the level (0104: 9.1 s at /Ob3,
# 9.4 s at /Ob1). The blow-up therefore comes from expanding the explicitly
# inline runtime helpers inside the very large generated entry functions, not
# from the units' size or control-flow density.
#
# Default to /Ob0 so the build completes. This costs AOT runtime performance --
# the object drops from 2.77 MB to 0.68 MB, so a lot of helper inlining is being
# given up. Raise this once the offending helper is identified and marked
# noinline individually.
# 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.
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]$")
message(FATAL_ERROR "PSPRECOMP_GENERATED_INLINE_LEVEL must be 0, 1, 2 or 3")
endif()
set(PSPRECOMP_HOT_GENERATED_INLINE_LEVEL "3" CACHE STRING
"MSVC /Ob level for measured hot VCS AOT units (0, 1, 2 or 3)")
set_property(CACHE PSPRECOMP_HOT_GENERATED_INLINE_LEVEL PROPERTY STRINGS 0 1 2 3)
if(NOT PSPRECOMP_HOT_GENERATED_INLINE_LEVEL MATCHES "^[0-3]$")
message(FATAL_ERROR "PSPRECOMP_HOT_GENERATED_INLINE_LEVEL must be 0, 1, 2 or 3")
endif()
file(GLOB VCS_GENERATED CONFIGURE_DEPENDS "${VCS_PROFILE_DIR}/generated/*.cpp")
if(MSVC)
@@ -61,10 +58,13 @@ 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.
set(VCS_HOT_UNIT_IDS
0018 0023 0024 0043 0044 0066 0067 0077 0080 0084 0087
0152 0154 0155 0169 0170 0173 0197 0200 0205 0216 0219)
set(VCS_HOT_SOURCES "${VCS_PROFILE_DIR}/generated/generated_registry.cpp")
0043)
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")
if(NOT EXISTS "${VCS_UNIT_SOURCE}")
@@ -81,7 +81,7 @@ if(PSPRECOMP_PROFILE_GUIDED_AOT)
set(VCS_HOT_MSVC_OPT "/O${PSPRECOMP_HOT_GENERATED_OPT_LEVEL}")
endif()
set(VCS_HOT_MSVC_OPTIONS
"${VCS_HOT_MSVC_OPT};/Ob${PSPRECOMP_GENERATED_INLINE_LEVEL};/bigobj;${PSPRECOMP_MSVC_MP_FLAG}")
"${VCS_HOT_MSVC_OPT};/Ob${PSPRECOMP_HOT_GENERATED_INLINE_LEVEL};/bigobj;${PSPRECOMP_MSVC_MP_FLAG}")
if(PSPRECOMP_LTO AND NOT PSPRECOMP_VCS_AOT_LTO)
string(APPEND VCS_HOT_MSVC_OPTIONS ";/GL-")
endif()
@@ -93,6 +93,25 @@ if(PSPRECOMP_PROFILE_GUIDED_AOT)
endif()
endif()
if(MSVC AND PSPRECOMP_NATIVE_AVX2)
# MSVC 19.44's AVX2 optimizer crashes (C1001/c2.dll) on this translation
# unit, while the identical /Ox /Ob0 compile completes in ~3 s with AVX.
# Keep AVX2 for the host and the other AOT units; append the weaker ISA only
# to the known compiler-bug trigger (the last /arch switch wins in cl.exe).
set(PSPRECOMP_VCS_MSVC_AVX_FALLBACK_UNITS "0018;0022;0035;0089;0091;0117;0164" CACHE STRING
"Generated VCS units forced to AVX because MSVC 19.44 crashes with AVX2")
set(VCS_MSVC_AVX_FALLBACK_SOURCES)
foreach(VCS_UNIT_ID IN LISTS PSPRECOMP_VCS_MSVC_AVX_FALLBACK_UNITS)
set(VCS_UNIT_SOURCE "${VCS_PROFILE_DIR}/generated/generated_unit_${VCS_UNIT_ID}.cpp")
if(NOT EXISTS "${VCS_UNIT_SOURCE}")
message(FATAL_ERROR "VCS AVX fallback unit is missing: ${VCS_UNIT_SOURCE}")
endif()
list(APPEND VCS_MSVC_AVX_FALLBACK_SOURCES "${VCS_UNIT_SOURCE}")
endforeach()
set_property(SOURCE ${VCS_MSVC_AVX_FALLBACK_SOURCES} APPEND PROPERTY
COMPILE_OPTIONS "/arch:AVX")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set_source_files_properties("${VCS_PROFILE_DIR}/host/vcs_profile.cpp" PROPERTIES COMPILE_OPTIONS "-O2")
endif()
@@ -224,6 +243,7 @@ if(PSPRECOMP_BUILD_PROFILE_TESTS)
endif()
add_executable(vcs_profile_tests tests/vcs_profile_tests.cpp ${VCS_HOST_SOURCES})
vcs_target_common(vcs_profile_tests)
vcs_set_runtime_output(vcs_profile_tests)
add_test(NAME vcs_profile_tests COMMAND vcs_profile_tests)
add_executable(vcs_config_tests
+1 -1
View File
@@ -48,7 +48,7 @@ GeometryDebugColors=false
DumpGpuFrameVblank=0
[Timing]
FrameRate=60
FrameRate=120
RealtimeSpeedDiagnostics=false
RealtimeSpeedIntervalVblanks=240
+4 -1
View File
@@ -31,7 +31,10 @@ void collision_fast_path(psprecomp::Runtime &runtime, psprecomp::AllegrexContext
static const bool enabled = [] {
if (truthy(std::getenv("PSPRECOMP_NO_FAST_088B1554"))) return false;
if (truthy(std::getenv("PSPRECOMP_VALIDATE_FAST_088B1554"))) return true;
return truthy(std::getenv("PSPRECOMP_ENABLE_FAST_088B1554"));
// This leaf has already matched the generated AOT reference across
// real gameplay validation runs. Keep it on in production; the NO_
// switch remains available for an immediate A/B or compatibility bisect.
return true;
}();
if (!enabled) {
+4 -1
View File
@@ -32,7 +32,7 @@ echo VCS - PERFORMANCE INCREMENTAL BUILD
echo Build pipeline restored to the last known-good pre-reorganization behavior.
echo CMake: !CMAKE_EXE!
echo Compile workers: %JOBS% ^| AOT /MP%JOBS% ^| MSBuild /m:1
echo AOT inlining: /Ob0 ^(MSVC optimizer blow-up workaround, see profile CMakeLists^)
echo AOT inlining: /Ob3 hot measured units ^| /Ob0 cold units
echo Link: host/core LTCG only ^| generated AOT /GL- ^| LTCG status visible
echo Build dir preserved: %BUILD%
echo ================================================================
@@ -47,7 +47,10 @@ echo [1/7] Configuring without deleting existing objects...
-DPSPRECOMP_AOT_PRODUCTION_FASTPATHS=ON ^
-DPSPRECOMP_MSVC_CGTHREADS=0 ^
-DPSPRECOMP_MSVC_MP_JOBS=%JOBS% ^
-DPSPRECOMP_PROFILE_GUIDED_AOT=ON ^
-DPSPRECOMP_HOT_GENERATED_OPT_LEVEL=3 ^
-DPSPRECOMP_GENERATED_INLINE_LEVEL=0 ^
-DPSPRECOMP_HOT_GENERATED_INLINE_LEVEL=3 ^
-DPSPRECOMP_VCS_AOT_LTO=OFF ^
-DPSPRECOMP_BUILD_TESTS=ON ^
-DPSPRECOMP_BUILD_PROFILE_TESTS=ON
+9 -9
View File
@@ -3,7 +3,8 @@ setlocal EnableExtensions
for %%I in ("%~dp0..\..\..") do set "REPO=%%~fI"
for %%I in ("%~dp0..") do set "PROFILE=%%~fI"
set "BIN=%REPO%\out\vcs-release\bin\Release\VCSNative.exe"
set "BIN=%REPO%\out\vcs-ninja\bin\Release\VCSNative.exe"
if not exist "%BIN%" set "BIN=%REPO%\out\vcs-release\bin\Release\VCSNative.exe"
if not exist "%BIN%" set "BIN=%REPO%\out\vcs-fast\bin\Release\VCSNative.exe"
if not exist "%BIN%" (
echo VCSNative.exe not found. Run build_release.bat or build_fast.bat first.
@@ -13,6 +14,7 @@ if not exist "%BIN%" (
set "GAME=%~1"
if "%GAME%"=="" if exist "%PROFILE%\game\PSP_GAME\SYSDIR\EBOOT_DECRYPTED.ELF" set "GAME=%PROFILE%\game"
if "%GAME%"=="" if exist "%~dp0PSP_DATA\PSP_GAME\SYSDIR\EBOOT_DECRYPTED.ELF" set "GAME=%~dp0PSP_DATA"
if "%GAME%"=="" if exist "%REPO%\out\vcs-dev\bin\Release\PSP_DATA\PSP_GAME\SYSDIR\EBOOT_DECRYPTED.ELF" set "GAME=%REPO%\out\vcs-dev\bin\Release\PSP_DATA"
if "%GAME%"=="" (
echo Game root not found. Pass it as the first argument or run prepare_game.ps1.
exit /b 4
@@ -35,22 +37,20 @@ set "PSPRECOMP_CONFIG=%PROFILE%\config\VCSNative.ini"
set "PSPRECOMP_GE_BACKEND=directx12"
set "PSPRECOMP_GE_GPU_TELEMETRY=0"
set "PSPRECOMP_GE_GPU_REPORT=0"
set "PSPRECOMP_GE_GPU_SKIP_SOFTWARE_RASTER=1"
set "PSPRECOMP_GE_ASYNC="
set "PSPRECOMP_GE_GPU_SKIP_SOFTWARE_RASTER="
set "PSPRECOMP_CHAIN_DEPTH="
set "PSPRECOMP_TIME_TICK_DISPATCHES="
set "PSPRECOMP_GE_GPU_HW_CULL=1"
set "PSPRECOMP_TIME_TICK_DISPATCHES=4096"
set "PSPRECOMP_DX12_DEBUG=0"
set "PSPRECOMP_DX12_GE_READBACK=0"
set "PSPRECOMP_DX12_GE_STRICT=0"
set "PSPRECOMP_DX12_TEXTURE_UPLOAD_RING=1"
set "PSPRECOMP_GE_ASYNC=1"
set "PSPRECOMP_GE_PARALLEL_VERTEX_DECODE=1"
set "PSPRECOMP_GE_PARALLEL_VERTEX_THRESHOLD=768"
set "PSPRECOMP_GE_PARALLEL_VERTEX_MAX_WORKERS=6"
set "PSPRECOMP_GE_PARALLEL_VERTEX_DECODE=0"
set "PSPRECOMP_RASTER_THREADS=4"
set "PSPRECOMP_GE_DIRECT_NONINDEXED_DRAW=1"
set "PSPRECOMP_DX12_PACKED_0115=1"
set "PSPRECOMP_DX12_NATIVE_INDEXED_DRAW=1"
set "PSPRECOMP_DX12_BATCH_MERGE=1"
set "PSPRECOMP_CHAIN_DEPTH=1024"
set "PSPRECOMP_ENABLE_FAST_088B1554=1"
set "PSPRECOMP_GE_GPU_DUAL_FRAME=0"
+5 -12
View File
@@ -254,8 +254,8 @@ bool Runtime::invoke_chained_call(AllegrexContext &ctx, GuestMemory::AotFastView
// Generation alone is sufficient: it increments on every PSP thread
// ownership change. Avoid constructing/checking a two-field token on every
// dynamic native chain boundary in the city hot path.
#if !defined(PSPRECOMP_AOT_PRODUCTION_FASTPATHS)
const std::uint64_t caller_generation = g_runtime_thread_switch_generation_fast;
#if !defined(PSPRECOMP_AOT_PRODUCTION_FASTPATHS)
if (g_pre_chained_call_hook != nullptr)
g_pre_chained_call_hook(*this, ctx, target_pc, native_depth);
#endif
@@ -275,21 +275,14 @@ bool Runtime::invoke_chained_call(AllegrexContext &ctx, GuestMemory::AotFastView
if (track_dispatch_counters_) ++chained_dispatches_;
#endif
#if defined(PSPRECOMP_AOT_PRODUCTION_FASTPATHS)
// Any scheduler boundary that actually switches PSP ownership marks the
// complete native chain invalid. The direct-chain path already uses this
// byte; dynamic JR/JALR chains can use the same invariant and avoid two
// process-global generation loads per call in production.
if (chain_context_invalidated_) {
(void)account_dispatch_work(ctx, false);
return false;
}
#else
// Dynamic targets may enter a profile/native function which switches PSP
// ownership without passing through run_starvation_boundary(). Keep the
// generation guard here even in production; compile-time direct chains use
// chain_context_invalidated_ and retain their cheaper hot path.
if (caller_generation != g_runtime_thread_switch_generation_fast) {
(void)account_dispatch_work(ctx, false);
return false;
}
#endif
return account_dispatch_work(ctx, true);
}