mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 22:01:34 +02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
6.0 KiB
C++
159 lines
6.0 KiB
C++
// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING).
|
|
#include <stdafx.h>
|
|
#include "host_cpu.h"
|
|
|
|
#ifdef __linux__
|
|
#include <sched.h>
|
|
#include <sys/prctl.h>
|
|
#endif
|
|
|
|
namespace hostcpu
|
|
{
|
|
#ifdef __linux__
|
|
namespace
|
|
{
|
|
// Set by ReserveFastCore before any other thread exists, read-only after.
|
|
bool s_reserving = false;
|
|
cpu_set_t s_reserved, s_shared;
|
|
|
|
uint64_t ReadCpuValue(int cpu, const char* file)
|
|
{
|
|
char path[96];
|
|
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/%s", cpu, file);
|
|
FILE* f = fopen(path, "r");
|
|
if (!f)
|
|
return 0;
|
|
unsigned long long value = 0;
|
|
if (fscanf(f, "%llu", &value) != 1)
|
|
value = 0;
|
|
fclose(f);
|
|
return value;
|
|
}
|
|
|
|
std::string CpuList(const cpu_set_t& set)
|
|
{
|
|
std::string list;
|
|
for (int cpu = 0; cpu < CPU_SETSIZE; cpu++)
|
|
if (CPU_ISSET(cpu, &set))
|
|
list += (list.empty() ? "" : ",") + std::to_string(cpu);
|
|
return list;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ReserveFastCore()
|
|
{
|
|
#ifdef __linux__
|
|
// NFSMW_CP_OWN_CORE=0: never; =fast: only on CPUs with fast and slow
|
|
// cores (the rule until 2026-09-29, when alike cores got one too).
|
|
const char* mode = std::getenv("NFSMW_CP_OWN_CORE");
|
|
if (mode && mode[0] == '0')
|
|
return;
|
|
bool fastOnly = mode && strcmp(mode, "fast") == 0;
|
|
cpu_set_t allowed;
|
|
if (sched_getaffinity(0, sizeof(allowed), &allowed) != 0)
|
|
return;
|
|
// Each CPU's top speed: cpuinfo_max_freq (Intel's hybrid cores), or
|
|
// where that is the same on every core, as amd-pstate reports it, the
|
|
// CPPC highest performance (196 on two of the Steam Machine's cores,
|
|
// 144 on the other four; 255 on each of the Deck's). A file that reads
|
|
// 0 everywhere isn't there: the other one tells.
|
|
struct Cpu { int cpu; uint64_t speed, core, capacity; };
|
|
std::vector<Cpu> cpus, read;
|
|
for (const char* file : { "cpufreq/cpuinfo_max_freq", "acpi_cppc/highest_perf" })
|
|
{
|
|
read.clear();
|
|
for (int cpu = 0; cpu < CPU_SETSIZE; cpu++)
|
|
if (CPU_ISSET(cpu, &allowed))
|
|
read.push_back({ cpu, ReadCpuValue(cpu, file),
|
|
ReadCpuValue(cpu, "topology/physical_package_id") << 32 | ReadCpuValue(cpu, "topology/core_id"),
|
|
ReadCpuValue(cpu, "cpu_capacity") });
|
|
if (std::all_of(read.begin(), read.end(), [](const Cpu& c) { return c.speed == 0; }))
|
|
continue;
|
|
cpus = read;
|
|
if (std::any_of(cpus.begin(), cpus.end(), [&](const Cpu& c) { return c.speed != cpus[0].speed; }))
|
|
break;
|
|
}
|
|
if (cpus.empty())
|
|
return;
|
|
auto [low, high] = std::minmax_element(cpus.begin(), cpus.end(), [](const Cpu& a, const Cpu& b) { return a.speed < b.speed; });
|
|
if (low->speed == 0)
|
|
return;
|
|
std::vector<uint64_t> cores, fastCores;
|
|
for (const Cpu& c : cpus)
|
|
{
|
|
if (std::find(cores.begin(), cores.end(), c.core) == cores.end())
|
|
cores.push_back(c.core);
|
|
if (c.speed * 100 >= high->speed * 95 && std::find(fastCores.begin(), fastCores.end(), c.core) == fastCores.end())
|
|
fastCores.push_back(c.core);
|
|
}
|
|
// A real split into fast and slow cores (the Steam Machine's slow
|
|
// ones top out at 73%) needs at least two fast physical cores, so the
|
|
// game's own threads keep one. Cores alike (a desktop Ryzen's
|
|
// preferred-core ranking differs by a few percent, the Deck's not at
|
|
// all) need at least four, so the other threads keep three: the
|
|
// whole game uses up to 2.3 cores in the fly-in, the command
|
|
// processor one of them. On the Deck its busy time fell 2-6%, with
|
|
// fewer late frames. The reserved core is the fast one
|
|
// with the most capacity (ARM: the kernel's cpu_capacity, which
|
|
// weighs a core's size too; the Steam Frame's Cortex-X4, cpu 7, tops
|
|
// out at 3.05 GHz to its A720s' 3.15 but does 20% more), then the
|
|
// last (cpu 0's core stays shared; x86 has no cpu_capacity).
|
|
bool split = low->speed * 100 <= high->speed * 85;
|
|
if (split ? cpus.size() < 6 || fastCores.size() < 2 : fastOnly || cores.size() < 4)
|
|
return;
|
|
uint64_t core = 0, capacity = 0;
|
|
for (const Cpu& c : cpus)
|
|
if (std::find(fastCores.begin(), fastCores.end(), c.core) != fastCores.end() &&
|
|
(c.capacity > capacity || (c.capacity == capacity && c.core > core)))
|
|
{
|
|
core = c.core;
|
|
capacity = c.capacity;
|
|
}
|
|
CPU_ZERO(&s_reserved);
|
|
s_shared = allowed;
|
|
for (const Cpu& c : cpus)
|
|
if (c.core == core)
|
|
{
|
|
CPU_SET(c.cpu, &s_reserved);
|
|
CPU_CLR(c.cpu, &s_shared);
|
|
}
|
|
if (sched_setaffinity(0, sizeof(s_shared), &s_shared) != 0)
|
|
return;
|
|
s_reserving = true;
|
|
fprintf(stderr, "[cpu] command processor: cpus %s (a %score of its own); other threads: cpus %s\n",
|
|
CpuList(s_reserved).c_str(), split ? "fast " : "", CpuList(s_shared).c_str());
|
|
#endif
|
|
}
|
|
|
|
void UseReservedCore(const char* who)
|
|
{
|
|
#ifdef __linux__
|
|
if (s_reserving && sched_setaffinity(0, sizeof(s_reserved), &s_reserved) != 0)
|
|
fprintf(stderr, "[cpu] %s: could not move to its reserved core\n", who);
|
|
#else
|
|
(void)who;
|
|
#endif
|
|
}
|
|
|
|
void LeaveReservedCore()
|
|
{
|
|
#ifdef __linux__
|
|
if (s_reserving)
|
|
sched_setaffinity(0, sizeof(s_shared), &s_shared);
|
|
#endif
|
|
}
|
|
|
|
void TightenTimerSlack()
|
|
{
|
|
#ifdef __linux__
|
|
thread_local bool done = false;
|
|
if (!done)
|
|
{
|
|
prctl(PR_SET_TIMERSLACK, 1000UL, 0, 0, 0);
|
|
done = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|