173 lines
6.4 KiB
C++
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
|