mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-08 13:50:20 +00:00
The worker could finish its loop before scePthreadCreate returned or before the first workerDone load, so the measured loop could be skipped with a no-op yield. scePthreadSetaffinity also only records the guest mask, so the two threads were never on one CPU. Both threads now pin their host threads to the CPU main starts on; the worker reports ready and waits for start, and the measured phase passes 64 turns back and forth, so neither thread can take its next turn until the other has run on that CPU. The test is registered on Windows and Linux, which have a thread affinity call. Co-Authored-By: Claude Opus 5.5 <[email protected]>
94 lines
2.4 KiB
C++
94 lines
2.4 KiB
C++
#include "prx/libkernel/include/Api.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libkernel/Pthread/include/Pthread.hpp"
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
|
|
#ifdef _WIN32
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#else
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#endif
|
|
|
|
static constexpr int SCE_OK = 0;
|
|
static constexpr int LONE_YIELDS = 4096;
|
|
static constexpr int ROUNDS = 64;
|
|
static constexpr std::uint64_t YIELD_LIMIT = 100000ull;
|
|
|
|
static void Require(bool value) { if (!value) std::abort(); }
|
|
|
|
static unsigned CurrentHostCpu() {
|
|
#ifdef _WIN32
|
|
return GetCurrentProcessorNumber();
|
|
#else
|
|
const int cpu = sched_getcpu();
|
|
Require(cpu >= 0 && cpu < CPU_SETSIZE);
|
|
return static_cast<unsigned>(cpu);
|
|
#endif
|
|
}
|
|
|
|
static bool PinHostThread(unsigned cpu) {
|
|
#ifdef _WIN32
|
|
return SetThreadAffinityMask(GetCurrentThread(), DWORD_PTR{1} << cpu) != 0;
|
|
#else
|
|
cpu_set_t one;
|
|
CPU_ZERO(&one);
|
|
CPU_SET(cpu, &one);
|
|
return pthread_setaffinity_np(pthread_self(), sizeof(one), &one) == 0;
|
|
#endif
|
|
}
|
|
|
|
static std::atomic<bool> workerPinned{false};
|
|
static std::atomic<bool> workerReady{false};
|
|
static std::atomic<bool> start{false};
|
|
static std::atomic<int> turn{0};
|
|
|
|
static void WaitTurn(int value, std::uint64_t& yields) {
|
|
while (turn.load() != value) {
|
|
scePthreadYield();
|
|
Require(++yields < YIELD_LIMIT);
|
|
}
|
|
}
|
|
|
|
static void* APS5_VABI PingPong(void* arg) {
|
|
const auto cpu = *static_cast<const unsigned*>(arg);
|
|
workerPinned.store(PinHostThread(cpu));
|
|
workerReady.store(true);
|
|
workerReady.notify_one();
|
|
start.wait(false);
|
|
std::uint64_t yields = 0;
|
|
for (int round = 0; round < ROUNDS; ++round) {
|
|
WaitTurn(2 * round + 1, yields);
|
|
turn.store(2 * round + 2);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int main() {
|
|
unsigned cpu = CurrentHostCpu();
|
|
Require(PinHostThread(cpu));
|
|
|
|
for (int call = 0; call < LONE_YIELDS; ++call) scePthreadYield();
|
|
|
|
Pthread worker = nullptr;
|
|
Require(scePthreadCreate(&worker, nullptr, PingPong, &cpu, nullptr) == SCE_OK);
|
|
workerReady.wait(false);
|
|
Require(workerPinned.load());
|
|
|
|
std::uint64_t yields = 0;
|
|
start.store(true);
|
|
start.notify_one();
|
|
for (int round = 0; round < ROUNDS; ++round) {
|
|
Require(turn.load() == 2 * round);
|
|
turn.store(2 * round + 1);
|
|
WaitTurn(2 * round + 2, yields);
|
|
}
|
|
Require(scePthreadJoin(worker, nullptr) == SCE_OK);
|
|
return 0;
|
|
}
|