AnyPS5/core/libs/prx/libc/include/CpuTopology.hpp

173 lines
6.4 KiB
C++

#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_CPUTOPOLOGY_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_CPUTOPOLOGY_HPP
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#elif defined(__linux__)
#include <fstream>
#include <pthread.h>
#include <sched.h>
#include <string>
#include <unistd.h>
#endif
// Hybrid CPU layout (Intel P/E cores) from the OS: which logical processors belong to the lowest and
// the highest efficiency class. Non-hybrid machines report no layout and every pin is a no-op unless
// a mask is given explicitly. Header-only so libkernel and the AGC driver share it without a link.
namespace CpuTopology {
struct Layout {
std::uint64_t process = 0;
std::uint64_t efficient = 0;
std::uint64_t performant = 0;
bool hybrid = false;
};
#ifdef __linux__
inline std::uint64_t MaskFromCpuList(const char* path) {
std::ifstream file(path);
std::string text;
if (!std::getline(file, text)) return 0;
std::uint64_t mask = 0;
const char* cursor = text.c_str();
while (*cursor != '\0') {
char* end = nullptr;
const unsigned long first = std::strtoul(cursor, &end, 10);
if (end == cursor) return 0;
unsigned long last = first;
cursor = end;
if (*cursor == '-') {
last = std::strtoul(cursor + 1, &end, 10);
if (end == cursor + 1 || last < first) return 0;
cursor = end;
}
for (unsigned long cpu = first; cpu <= last && cpu < 64; ++cpu) mask |= std::uint64_t{1} << cpu;
if (*cursor == ',') ++cursor;
else if (*cursor != '\0') return 0;
}
return mask;
}
#endif
inline const Layout& Get() {
static const Layout layout = [] {
Layout result{};
#ifdef _WIN32
DWORD_PTR processMask = 0;
DWORD_PTR systemMask = 0;
if (GetProcessAffinityMask(GetCurrentProcess(), &processMask, &systemMask)) result.process = static_cast<std::uint64_t>(processMask);
DWORD length = 0;
GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &length);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || length == 0) return result;
std::vector<unsigned char> buffer(length);
if (!GetLogicalProcessorInformationEx(RelationProcessorCore, reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(buffer.data()), &length)) return result;
std::uint64_t byClass[256] = {};
bool seen[256] = {};
for (DWORD offset = 0; offset + 8 <= length;) {
const auto* entry = reinterpret_cast<const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(buffer.data() + offset);
if (entry->Size == 0 || offset + entry->Size > length) break;
if (entry->Relationship == RelationProcessorCore && entry->Processor.GroupCount >= 1 && entry->Processor.GroupMask[0].Group == 0) {
const unsigned cls = entry->Processor.EfficiencyClass;
byClass[cls] |= static_cast<std::uint64_t>(entry->Processor.GroupMask[0].Mask);
seen[cls] = true;
}
offset += entry->Size;
}
int lowest = -1;
int highest = -1;
for (int cls = 0; cls < 256; ++cls) {
if (!seen[cls]) continue;
if (lowest < 0) lowest = cls;
highest = cls;
}
if (lowest < 0 || lowest == highest) return result;
result.efficient = byClass[lowest];
result.performant = byClass[highest];
result.hybrid = result.efficient != 0 && result.performant != 0;
#elif defined(__linux__)
cpu_set_t set;
CPU_ZERO(&set);
if (sched_getaffinity(0, sizeof(set), &set) == 0) {
for (unsigned cpu = 0; cpu < 64; ++cpu)
if (CPU_ISSET(cpu, &set)) result.process |= std::uint64_t{1} << cpu;
}
result.performant = MaskFromCpuList("/sys/devices/cpu_core/cpus");
result.efficient = MaskFromCpuList("/sys/devices/cpu_atom/cpus");
result.hybrid = result.efficient != 0 && result.performant != 0 && (result.efficient & result.performant) == 0;
#endif
return result;
}();
return layout;
}
// A hex mask from the environment, 0 when unset or empty.
inline std::uint64_t MaskFromEnvironment(const char* name) {
const char* text = std::getenv(name);
if (text == nullptr || *text == '\0') return 0;
return std::strtoull(text, nullptr, 16);
}
inline bool Trace() {
static const bool trace = std::getenv("APS5_TRACE_AFFINITY") != nullptr;
return trace;
}
inline unsigned long ThreadId(void* handle) {
#ifdef _WIN32
return handle != nullptr ? GetThreadId(static_cast<HANDLE>(handle)) : GetCurrentThreadId();
#elif defined(__linux__)
return handle == nullptr ? static_cast<unsigned long>(gettid()) : 0;
#else
(void)handle;
return 0;
#endif
}
// Restricts the thread (`nullptr` is the calling thread) to `mask` clipped to the process affinity;
// returns the mask applied, 0 when nothing was.
inline std::uint64_t Pin(void* handle, std::uint64_t mask) {
#ifdef _WIN32
const auto& layout = Get();
if (layout.process != 0) mask &= layout.process;
if (mask == 0) return 0;
HANDLE thread = handle != nullptr ? static_cast<HANDLE>(handle) : GetCurrentThread();
return SetThreadAffinityMask(thread, static_cast<DWORD_PTR>(mask)) != 0 ? mask : 0;
#elif defined(__linux__)
const auto& layout = Get();
if (layout.process != 0) mask &= layout.process;
if (mask == 0 || handle != nullptr) return 0;
cpu_set_t set;
CPU_ZERO(&set);
for (unsigned cpu = 0; cpu < 64; ++cpu)
if ((mask >> cpu) & 1u) CPU_SET(cpu, &set);
return pthread_setaffinity_np(pthread_self(), sizeof(set), &set) == 0 ? mask : 0;
#else
(void)handle;
(void)mask;
return 0;
#endif
}
// Pin plus the APS5_TRACE_AFFINITY=1 line naming the thread.
inline std::uint64_t PinTraced(const char* role, void* handle, std::uint64_t mask) {
const auto applied = Pin(handle, mask);
if (Trace()) std::fprintf(stderr, "[affinity] %s tid=%lu mask=0x%llx%s\n", role, ThreadId(handle), static_cast<unsigned long long>(mask), applied != 0 ? "" : " (not applied)");
return applied;
}
// Helper threads (deferred release, mirror refresh) are pinned only when APS5_HELPER_AFFINITY_MASK
// names their cores.
inline void PinHelperThread(const char* role) {
static const std::uint64_t mask = MaskFromEnvironment("APS5_HELPER_AFFINITY_MASK");
if (mask != 0) PinTraced(role, nullptr, mask);
}
}
#endif