Files
ppsspp/libretro/libretro-common/features/cpu_class.h
T
libretroadmin cf9d102864 libretro: vendor libretro-common from RetroArch in-tree
The libretro-common submodule pinned the standalone repository at
76a3d54. It is replaced by an in-tree copy of RetroArch master
(b655a20) holding only what the core uses: the VFS, file, string,
encoding, compat and time sources it already built, the headers they and
the core include, the threading set - rthreads, retro_atomic, retro_spsc
and retro_waitable_spsc, mpsc_stack, retro_triple_buffer,
retro_eventcount, retro_asym_eventcount, retro_procbarrier and tpool,
with features_cpu and rstrtod, which they need - and the codecs that
replace the bundled zlib, zstd, libchdr, libzip and libpng: rinflate and
rdeflate, rzstd, rchd with its LZMA and FLAC decoders, rzip_archive,
rpng and crc32.

stdstring.c, which nothing calls, is no longer built. The threading sources are always built, by both the static Makefile and
CMake; the VFS set stays behind HAVE_LIBRETRO_VFS as before. The codecs
are built by the change that switches the core over to them.
2026-10-08 14:14:37 +02:00

266 lines
9.1 KiB
C

/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cpu_class.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* Per-processor performance class: the one source of truth shared by
* cpu_features_get_processor_order() (features_cpu.c) and
* sthread_prefer_fast_cores() / sthread_get_core_topology()
* (rthreads.c), so a thread pinned "to the fast cores" and a thread
* pinned "to the strongest processor" land on the same silicon.
*
* This is a private implementation header: every function is static
* and the file is included by exactly the two .c files above, because
* neither may link the other (rthreads.c is built on its own by the
* sample harnesses, features_cpu.c by the cores).
*
* cpu_class_read() fills cls[id] for id < len with a class where a
* higher value is a faster core, and returns the number of ids
* covered, or 0 when the platform publishes no class at all - a
* homogeneous part, or one this code cannot see into - in which case
* every processor is to be taken as the same class.
*
* Linux, in order of trust:
* 1. The kernel's own hybrid classification: on Intel hybrid parts
* the perf driver publishes the P-cores as cpu_core/cpus and the
* E-cores as cpu_atom/cpus.
* 2. cpu_capacity, the scheduler's per-CPU throughput figure on ARM
* big.LITTLE, where the biggest cluster is 1024 and a little one a
* few hundred. A three-tier part (one prime, some big, some
* little) keeps its big cores in the fast class by taking
* everything within CPU_CLASS_CAPACITY_PCT of the best.
* 3. cpuinfo_max_freq, within CPU_CLASS_FREQ_PCT of the highest. The
* band is what makes this usable on x86: favoured cores (Intel
* Turbo Boost Max 3.0, AMD CPPC preferred cores) and the two CCDs
* of an X3D part differ from their siblings by a few percent,
* while a real slow cluster sits 20-40% lower.
* Windows 10 1607+: CPU Sets EfficiencyClass, which rises with
* performance (P = 1, E = 0 on Intel hybrid). Resolved at run time so
* older targets still link and simply report nothing.
* Everything else: nothing.
*
* A builder may point CPU_CLASS_SYSFS at a fixture tree in place of
* /sys/devices/system/cpu. */
#ifndef __LIBRETRO_SDK_CPU_CLASS_H
#define __LIBRETRO_SDK_CPU_CLASS_H
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <boolean.h>
#include <retro_inline.h>
#ifndef CPU_CLASS_SYSFS
#define CPU_CLASS_SYSFS "/sys/devices/system/cpu"
#endif
#define CPU_CLASS_CAPACITY_PCT 70
#define CPU_CLASS_FREQ_PCT 85
#define CPU_CLASS_MAX_IDS 256
#if defined(__linux__)
/* Reads one unsigned long from a sysfs file; 0 when absent or empty. */
static unsigned long cpu_class_sysfs_ulong(const char *path)
{
unsigned long v = 0;
FILE *f = fopen(path, "r");
if (!f)
return 0;
if (fscanf(f, "%lu", &v) != 1)
v = 0;
fclose(f);
return v;
}
/* Parses a sysfs cpulist ("0-7,16-23") at path, setting mark[id] for
* each id < len it names; returns whether the file existed and named
* at least one processor. */
static bool cpu_class_sysfs_cpulist(const char *path,
unsigned char *mark, size_t len)
{
char line[512];
char *p;
bool any = false;
FILE *f = fopen(path, "r");
if (!f)
return false;
line[0] = '\0';
if (!fgets(line, sizeof(line), f))
line[0] = '\0';
fclose(f);
for (p = line; *p; )
{
char *next;
unsigned long lo, hi, i;
if (*p < '0' || *p > '9')
{
p++;
continue;
}
lo = hi = strtoul(p, &next, 10);
if (*next == '-')
hi = strtoul(next + 1, &next, 10);
for (i = lo; i <= hi && i < len; i++)
{
mark[i] = 1;
any = true;
}
p = next;
}
return any;
}
/* Reads "<cpu>/<leaf>" for every id < len; sets cls[id] to 1 where the
* value is within pct percent of the highest, 0 otherwise (including
* ids with no value). Returns the highest id with a value plus one, or
* 0 when none had one. */
static size_t cpu_class_by_value(const char *leaf, unsigned pct,
unsigned char *cls, size_t len)
{
unsigned long best = 0;
size_t i, n = 0;
/* 32-bit is plenty: kHz clocks top out below 10^7 and capacities
* at 1024; unsigned long would double the frame past the 2 KiB
* thread-stack budget (PSP, GX). */
unsigned vals[CPU_CLASS_MAX_IDS];
if (len > CPU_CLASS_MAX_IDS)
len = CPU_CLASS_MAX_IDS;
for (i = 0; i < len; i++)
{
char path[256];
snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/%s",
(unsigned)i, leaf);
vals[i] = (unsigned)cpu_class_sysfs_ulong(path);
if (vals[i])
{
n = i + 1;
if (vals[i] > best)
best = vals[i];
}
}
for (i = 0; i < len; i++)
cls[i] = (vals[i] && vals[i] * 100 >= best * pct) ? 1 : 0;
return n;
}
static size_t cpu_class_read(unsigned char *cls, size_t len)
{
unsigned char atom[CPU_CLASS_MAX_IDS];
size_t i, n;
if (len > CPU_CLASS_MAX_IDS)
len = CPU_CLASS_MAX_IDS;
memset(cls, 0, len);
memset(atom, 0, sizeof(atom));
/* 1. Intel hybrid, as the kernel sees it (/sys/devices/cpu_core and
* cpu_atom, beside /sys/devices/system). Only meaningful when
* both kinds exist: cpu_core alone is what a homogeneous Intel
* part publishes. */
if ( cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_core/cpus", cls, len)
&& cpu_class_sysfs_cpulist(CPU_CLASS_SYSFS "/../../cpu_atom/cpus", atom, len))
{
n = 0;
for (i = 0; i < len; i++)
if (cls[i] || atom[i])
n = i + 1;
return n;
}
memset(cls, 0, len);
/* 2. Scheduler capacity (ARM), then 3. maximum clock. */
if ((n = cpu_class_by_value("cpu_capacity", CPU_CLASS_CAPACITY_PCT, cls, len)))
return n;
return cpu_class_by_value("cpufreq/cpuinfo_max_freq", CPU_CLASS_FREQ_PCT,
cls, len);
}
#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
#include <windows.h>
typedef BOOL (WINAPI *cpu_class_get_cpusets_t)(void*, ULONG, ULONG*,
HANDLE, ULONG);
/* SYSTEM_CPU_SET_INFORMATION, read by offset since older SDKs lack the
* type: Size at 0, Type at 4 (0 = CpuSet), Id at 8, Group at 12,
* LogicalProcessorIndex at 14, CoreIndex at 15, EfficiencyClass at 18.
* Ids are the processor's index within group 0, the numbering an
* affinity mask is built from; other groups are beyond a 64-bit mask
* and are left out. */
static size_t cpu_class_read(unsigned char *cls, size_t len)
{
HMODULE k32 = GetModuleHandleA("kernel32.dll");
cpu_class_get_cpusets_t getinfo = NULL;
unsigned char *buf = NULL;
ULONG len_needed = 0;
ULONG off;
size_t n = 0;
memset(cls, 0, len);
if (k32)
getinfo = (cpu_class_get_cpusets_t)(void (*)(void))
GetProcAddress(k32, "GetSystemCpuSetInformation");
if (!getinfo)
return 0;
getinfo(NULL, 0, &len_needed, GetCurrentProcess(), 0);
if (!len_needed || !(buf = (unsigned char*)malloc(len_needed)))
return 0;
if (!getinfo(buf, len_needed, &len_needed, GetCurrentProcess(), 0))
{
free(buf);
return 0;
}
for (off = 0; off + 20 <= len_needed; )
{
DWORD size = *(DWORD*)(buf + off);
if (size < 20)
break;
if (*(DWORD*)(buf + off + 4) == 0 && *(WORD*)(buf + off + 12) == 0)
{
unsigned id = buf[off + 14];
if (id < len)
{
cls[id] = buf[off + 18];
if ((size_t)id + 1 > n)
n = id + 1;
}
}
off += size;
}
free(buf);
return n;
}
#else
/* No class source: inline so a platform whose callers are compiled
* out of the ranking does not carry an unreferenced function. */
static INLINE size_t cpu_class_read(unsigned char *cls, size_t len)
{
memset(cls, 0, len);
return 0;
}
#endif
#endif