Files
AnyPS5/core/libs/tests/CpuTopology.cpp
T
Dean Galvin 604826c138 perf(agc): pin the queue workers to the performance cores on Linux
CpuTopology::Get and Pin did nothing off Windows, so on a hybrid CPU the
queue workers, the draw front end and the presenter ran wherever the
scheduler put them. On a Linux kernel that reports the same capacity for
every CPU, those threads spent part of each frame on the efficiency cores.

Get now reads the process affinity with sched_getaffinity and the core
classes from /sys/devices/cpu_core/cpus and /sys/devices/cpu_atom/cpus, and
Pin restricts the calling thread with pthread_setaffinity_np. A CPU is
hybrid only when both lists exist and are disjoint, so non-hybrid CPUs are
never pinned. On Linux the workers are pinned to the performance cores of
a hybrid CPU by default; APS5_NO_WORKER_AFFINITY=1 leaves them free.
Windows keeps the APS5_WORKER_AFFINITY=1 opt-in, and
APS5_WORKER_AFFINITY_MASK=<hex> still picks the cores on any platform.

PPSA21564 on a hybrid CPU, pinned against unpinned (2 runs each): title
46.7 to 49.0 fps, menu 45.4 to 47.7, intro level 37.2 to 41.3 (space) and
31.7 to 37.6 (ships). Hub descent 6.56 to 7.92 fps (3 runs each).
2026-10-05 21:23:34 +13:00

50 lines
2.1 KiB
C++

#include "prx/libc/include/CpuTopology.hpp"
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
static void Require(bool value, const char* what) {
if (value) return;
std::fprintf(stderr, "cpu topology test failed: %s\n", what);
std::exit(1);
}
static std::uint64_t Parse(const char* text) {
const auto path = std::filesystem::temp_directory_path() / "anyps5_cpu_topology_test_cpus";
{
std::ofstream file(path, std::ios::trunc);
file << text;
}
const auto mask = CpuTopology::MaskFromCpuList(path.c_str());
std::filesystem::remove(path);
return mask;
}
int main() {
Require(Parse("0-15\n") == 0xffffull, "range");
Require(Parse("16-31\n") == 0xffff0000ull, "high range");
Require(Parse("0,2,4-5\n") == 0x35ull, "list of cpus and ranges");
Require(Parse("3") == 0x8ull, "single cpu without a newline");
Require(Parse("62-70\n") == 0xc000000000000000ull, "cpus past 63 are left out");
Require(Parse("") == 0, "empty list");
Require(Parse("4-2\n") == 0, "reversed range");
Require(Parse("0-x\n") == 0, "malformed range");
Require(Parse("0;1\n") == 0, "malformed separator");
Require(CpuTopology::MaskFromCpuList("/nonexistent/anyps5/cpus") == 0, "missing file");
const auto& layout = CpuTopology::Get();
Require(layout.process != 0, "process affinity read");
Require(!layout.hybrid || (layout.performant & layout.efficient) == 0, "hybrid classes overlap");
const std::uint64_t first = layout.process & (~layout.process + 1);
Require(CpuTopology::Pin(nullptr, first) == first, "pin to one cpu of the process");
cpu_set_t set;
CPU_ZERO(&set);
Require(pthread_getaffinity_np(pthread_self(), sizeof(set), &set) == 0, "read thread affinity");
Require(CPU_COUNT(&set) == 1 && CPU_ISSET(static_cast<unsigned>(__builtin_ctzll(first)), &set), "thread runs on the pinned cpu only");
Require(CpuTopology::Pin(nullptr, ~layout.process) == 0, "mask outside the process is not applied");
int dummy = 0;
Require(CpuTopology::Pin(&dummy, first) == 0, "other threads are not pinned by handle");
return 0;
}