diff --git a/pcsx2-eerunner/Main.cpp b/pcsx2-eerunner/Main.cpp index 74f54962f8..6216a961c9 100644 --- a/pcsx2-eerunner/Main.cpp +++ b/pcsx2-eerunner/Main.cpp @@ -43,6 +43,7 @@ #include "common/FileSystem.h" #include "common/MemorySettingsInterface.h" #include "common/Path.h" +#include "common/Perf.h" #include "common/ProgressCallback.h" #include "common/StringUtil.h" @@ -100,6 +101,7 @@ static bool s_no_console = false; static bool s_contmem_vu0_interp = false; // --vu0-interp modifier for --contmem static GSRendererType s_renderer = GSRendererType::Null; // --renderer (Null default; vk for Intel/headless) static std::string s_memdump_prefix; // --memdump : write .{interp,jit}.bin at the last frame +static bool s_perf_jitdump = false; // --perf-jitdump: emit Linux perf jitdump for `perf inject --jit` (profiling) bool EERunner::InitializeConfig() { @@ -497,6 +499,9 @@ static void PrintCommandLineHelp(const char* progname) std::fprintf(stderr, " --savestate : Savestate to load after Initialize (required).\n"); std::fprintf(stderr, " --frames N: Number of frames to run (default 300).\n"); std::fprintf(stderr, " --iso : Game ISO/disc to mount (required so the savestate has its disc).\n"); + std::fprintf(stderr, " --perf-jitdump: Emit a Linux perf jitdump (under EmuFolders::Cache) so `perf inject --jit`\n"); + std::fprintf(stderr, " resolves EE_/VU0_/VU1_/IOP_/VIF_ JIT block symbols. Profiling only; with --liverun it\n"); + std::fprintf(stderr, " also honors an explicit --renderer null. Requires a USE_PERF_JITDUMP build.\n"); std::fprintf(stderr, " -help: Displays this information and exits.\n"); std::fprintf(stderr, " -version: Displays version information and exits.\n"); std::fprintf(stderr, "\n"); @@ -570,6 +575,15 @@ bool EERunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa s_mode = RunMode::LiveRun; continue; } + else if (CHECK_ARG("--perf-jitdump")) + { + // Emit a Linux perf jitdump so `perf inject --jit` can resolve EE_/VU1_/... + // JIT block symbols. Profiling only; honors explicit --renderer null (the + // liverun null->VK force below is skipped when this is set). Requires a + // USE_PERF_JITDUMP build (no-op otherwise). + s_perf_jitdump = true; + continue; + } else if (CHECK_ARG("--disasm")) { // --disasm: load the savestate, then disassemble EE code in @@ -700,7 +714,11 @@ void EERunner::SettingsOverride() // like Null) and MTVU. Null GS is meaningless for it, so force VK if unset. const bool live = (s_mode == RunMode::LiveRun); GSRendererType rend = s_renderer; - if (live && rend == GSRendererType::Null) + // Liverun normally needs a real GS (Null drops GIF/PATH3), so Null is forced to VK. + // When profiling (--perf-jitdump), honor an explicit --renderer null so the + // "scalar EE/IOP minus GS-feeding" diagnostic baseline is reachable. vk stays the + // representative whole-system profile; null is the secondary diagnostic. + if (live && rend == GSRendererType::Null && !s_perf_jitdump) rend = GSRendererType::VK; s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast(rend)); @@ -778,6 +796,11 @@ void EERunner::SettingsOverride() s_settings_interface.SetStringValue("SPU2/Output", "Backend", "Null"); s_settings_interface.SetStringValue("SPU2/Output", "SyncMode", "Disabled"); + // Profiling: drive the perf jitdump enable through the normal config path so + // ApplySettings/LoadSettings (which re-applies Perf::SetJitDumpEnabled from this + // bool) keeps it on for the whole run instead of resetting it to the default. + s_settings_interface.SetBoolValue("EmuCore/Profiler", "EnablePerfDump", s_perf_jitdump); + // No frameskip. s_settings_interface.SetBoolValue("EmuCore/GS", "FrameSkipEnable", false); s_settings_interface.SetIntValue("EmuCore/GS", "FramesToDraw", 1); @@ -2889,6 +2912,17 @@ static void CPUThreadMain(VMBootParameters* params, std::atomic* ret) if (VMManager::Internal::CPUThreadInitialize()) { + // Profiling: set the jitdump output dir before any JIT block compiles (the + // first compile happens during the first FrameAdvance, well after this). Dir = + // EmuFolders::Cache so the 100s-of-MB dump avoids /tmp/tmpfs, matching the + // production rationale in common/Perf.cpp. The ENABLE flag is driven through the + // normal settings path instead (EmuCore/Profiler EnablePerfDump, set in the + // harness config) — ApplySettings() below calls LoadSettings() which re-applies + // Perf::SetJitDumpEnabled(EnablePerfDump), so a manual enable here would just get + // reset to the config default (false). No-op on non-jitdump builds. + if (s_perf_jitdump) + Perf::SetJitDumpDir(EmuFolders::Cache); + // apply new settings (e.g. pick up renderer change) VMManager::ApplySettings(); diff --git a/pcsx2-vurunner/Main.cpp b/pcsx2-vurunner/Main.cpp index 4e4ff5cc87..5a271cca78 100644 --- a/pcsx2-vurunner/Main.cpp +++ b/pcsx2-vurunner/Main.cpp @@ -33,6 +33,7 @@ #include "DebugTools/Debug.h" #include "common/FPControl.h" +#include "common/Perf.h" #include "common/PmuCounters.h" #include @@ -63,6 +64,7 @@ struct Options bool bench_no_reprime = false; bool print_bases = false; bool no_progcache = false; // determinism gate: force program cache + recording off + bool perf_jitdump = false; // emit Linux perf jitdump for `perf inject --jit` (profiling) u32 dump_count = 64; u32 cycle_override = 0; // 0 = use captured budget int vu_clamp_mode = -1; // -1 = leave EmuConfig default (mode 1); 0..3 = force VU clamp mode @@ -173,6 +175,10 @@ bool ParseArgs(int argc, char** argv, Options& opts) { opts.no_progcache = true; } + else if (a == "--perf-jitdump") + { + opts.perf_jitdump = true; + } else if (a == "--cache-dir") { if (i + 1 >= argc) @@ -1188,6 +1194,14 @@ int main(int argc, char** argv) return 1; } + // Profiling (--perf-jitdump): enable the perf jitdump writer as early as possible + // — before ANY VU block compiles — so `perf inject --jit` resolves VU0_/VU1_ + // symbols. Dir defaults to /tmp (EmuFolders::Cache isn't populated this early in + // the harness; fine for the tiny per-program dumps vurunner emits). No-op on + // non-USE_PERF_JITDUMP builds. + if (opts.perf_jitdump) + Perf::SetJitDumpEnabled(true); + #if defined(_M_ARM64) || defined(__aarch64__) if (opts.no_progcache) mVUPersist::SetProcessDisable(true); diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore new file mode 100644 index 0000000000..7a60b85e14 --- /dev/null +++ b/tools/perf/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/tools/perf/README.md b/tools/perf/README.md new file mode 100644 index 0000000000..775f973711 --- /dev/null +++ b/tools/perf/README.md @@ -0,0 +1,49 @@ +# tools/perf — PCSX2 ARM64 CPU profiling rig + +Step 0 of the `neither` cherry-pick funnel (`/home/bmd/pcsx2/neither/CLAUDE.md`): get a +**current, repeatable, attributable** bottleneck baseline for our own port. The old +RK3562 numbers are stale and from the wrong device; we re-profile on **M2 Max / Asahi** +first, then SD865. + +## Pieces + +| File | Role | +|---|---| +| `bucket_perf.py` | parse a `perf report --stdio` dump → subsystem ranking (EE-JIT/VU0/VU1/IOP/VIF-JIT + GS/SPU2/vtlb/dispatcher/sync/kernel) + thread-comm axis; `--json` for aggregation. Pure stdlib. | +| `profile_run.sh` | one-command wrapper: precondition gate → `perf record`/`inject --jit`/`report` → bucket → median wallclock + median shares → `summary.md`. Device-parameterized. | +| `devices/