From e8c39ed1a7905994366634f0febae484ffb6870c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 30 Sep 2026 11:11:22 -0600 Subject: [PATCH] Unit tests: Share the benchmark timing loop Four tests had their own copy of "call this until N seconds have passed, then divide". CallsPerSecond in UnitTest.h does it; each keeps its old duration and batch size. Co-Authored-By: Claude Opus 5.5 (1M context) --- unittest/JitHarness.cpp | 22 ++++++++-------------- unittest/TestMpegCsc.cpp | 32 +++++++++----------------------- unittest/TestVertexJit.cpp | 12 +----------- unittest/UnitTest.h | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 48 deletions(-) diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index a01bea0f7b..5d1f59fb6f 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -37,6 +37,7 @@ #include "Core/CoreTiming.h" #include "Core/Config.h" #include "Core/HLE/HLE.h" +#include "unittest/UnitTest.h" void UnitTestTerminator() { // Bails out of jit so we can time things. @@ -50,27 +51,20 @@ HLEFunction UnitTestFakeSyscalls[] = { double ExecCPUTest(bool clearCache = true) { int blockTicks = 1000000; - int total = 0; if (MIPSComp::jit) { currentMIPS->pc = PSP_GetUserMemoryBase(); MIPSComp::JitAt(currentMIPS); } - double st = time_now_d(); - do { - for (int j = 0; j < 1000; ++j) { - currentMIPS->pc = PSP_GetUserMemoryBase(); - coreState = CORE_RUNNING_CPU; + const double callsPerSecond = CallsPerSecond([&] { + currentMIPS->pc = PSP_GetUserMemoryBase(); + coreState = CORE_RUNNING_CPU; - while (coreState == CORE_RUNNING_CPU) { - mipsr4k.RunLoopUntil(blockTicks); - } - ++total; + while (coreState == CORE_RUNNING_CPU) { + mipsr4k.RunLoopUntil(blockTicks); } - } - while (time_now_d() - st < 0.5); - double elapsed = time_now_d() - st; + }, 0.5, 1000); if (MIPSComp::jit) { JitBlockCacheDebugInterface *cache = MIPSComp::jit->GetBlockCacheDebugInterface(); @@ -83,7 +77,7 @@ double ExecCPUTest(bool clearCache = true) { MIPSComp::jit->ClearCache(); } - return total / elapsed; + return callsPerSecond; } static void SetupJitHarness() { diff --git a/unittest/TestMpegCsc.cpp b/unittest/TestMpegCsc.cpp index 5e20b7d40b..cf9792988d 100644 --- a/unittest/TestMpegCsc.cpp +++ b/unittest/TestMpegCsc.cpp @@ -154,18 +154,11 @@ static double MeasureMegapixelsPerSecond(const TestFrame &frame, int pixelMode, CscFunc fn = &MpegCscRange) { const int destStride = 512; // Long enough to swamp the clock's own resolution, short enough not to pad the test run. - const double seconds = 0.2; - int frames = 0; - const double start = time_now_d(); - do { - for (int i = 0; i < 4; i++) { - fn(dest.data(), destStride, pixelMode, frame.luma.data(), frame.cb.data(), - frame.cr.data(), frame.width, 0, 0, frame.width, frame.height); - frames++; - } - } while (time_now_d() - start < seconds); - const double elapsed = time_now_d() - start; - return (double)frames * frame.width * frame.height / elapsed / 1000000.0; + const double framesPerSecond = CallsPerSecond([&] { + fn(dest.data(), destStride, pixelMode, frame.luma.data(), frame.cb.data(), + frame.cr.data(), frame.width, 0, 0, frame.width, frame.height); + }, 0.2, 4); + return framesPerSecond * frame.width * frame.height / 1000000.0; } // The de-tiling as it was originally written, straight from the description of the layout: bounds @@ -243,17 +236,10 @@ static double MeasureUntileMegapixelsPerSecond(const TiledFrame &tiled, int widt u8 *luma = planes.data(); u8 *cb = luma + (size_t)width * height; u8 *cr = cb + (size_t)(width / 2) * (height / 2); - const double seconds = 0.2; - int frames = 0; - const double start = time_now_d(); - do { - for (int i = 0; i < 4; i++) { - fn(luma, cb, cr, tiled.src, tiled.sizes, width, height); - frames++; - } - } while (time_now_d() - start < seconds); - const double elapsed = time_now_d() - start; - return (double)frames * width * height / elapsed / 1000000.0; + const double framesPerSecond = CallsPerSecond([&] { + fn(luma, cb, cr, tiled.src, tiled.sizes, width, height); + }, 0.2, 4); + return framesPerSecond * width * height / 1000000.0; } bool TestMpegCsc() { diff --git a/unittest/TestVertexJit.cpp b/unittest/TestVertexJit.cpp index 874ea965e3..2dc8a1db23 100644 --- a/unittest/TestVertexJit.cpp +++ b/unittest/TestVertexJit.cpp @@ -81,17 +81,7 @@ public: double ExecuteTimed(int vtype, int count, bool useJit) { SetupExecute(vtype, useJit); - int total = 0; - double st = time_now_d(); - do { - for (int j = 0; j < ROUNDS; ++j) { - dec_->DecodeVerts(dst_, src_, &g_uvScale, count); - ++total; - } - } while (time_now_d() - st < 0.5); - double elapsed = time_now_d() - st; - - return total / elapsed; + return CallsPerSecond([&] { dec_->DecodeVerts(dst_, src_, &g_uvScale, count); }, 0.5, ROUNDS); } void Add8(u8 x) { diff --git a/unittest/UnitTest.h b/unittest/UnitTest.h index 35f42f53af..1d38e94a2d 100644 --- a/unittest/UnitTest.h +++ b/unittest/UnitTest.h @@ -5,6 +5,24 @@ #include #include +#include "Common/TimeUtil.h" + +// For benchmarks: calls fn over and over, callsPerBatch at a time between reads of the clock, for at +// least the given number of seconds, and returns how many calls per second that came to. Multiply by +// the work one call does (pixels, vertices) for a throughput. +template +double CallsPerSecond(Func fn, double seconds, int callsPerBatch) { + int calls = 0; + const double start = time_now_d(); + do { + for (int i = 0; i < callsPerBatch; i++) { + fn(); + } + calls += callsPerBatch; + } while (time_now_d() - start < seconds); + return calls / (time_now_d() - start); +} + inline bool rel_equal(float a, float b, float precision) { float diff = fabsf(a - b); if (diff == 0.0f) {