TimeUtil: Minor cleanup, add precise_sleep()

This commit is contained in:
Henrik Rydgård 2024-07-26 11:16:41 +02:00
parent 5926886c0c
commit 96c4ae4457
9 changed files with 103 additions and 35 deletions

View File

@ -41,20 +41,27 @@ static LARGE_INTEGER frequency;
static double frequencyMult;
static LARGE_INTEGER startTime;
static inline void InitTime() {
if (frequency.QuadPart == 0) {
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
frequencyMult = 1.0 / static_cast<double>(frequency.QuadPart);
}
HANDLE Timer;
int SchedulerPeriodMs;
INT64 QpcPerSecond;
void TimeInit() {
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
QpcPerSecond = frequency.QuadPart;
frequencyMult = 1.0 / static_cast<double>(frequency.QuadPart);
Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
TIMECAPS caps;
timeGetDevCaps(&caps, sizeof caps);
timeBeginPeriod(caps.wPeriodMin);
SchedulerPeriodMs = (int)caps.wPeriodMin;
}
double time_now_d() {
InitTime();
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
double elapsed = static_cast<double>(time.QuadPart - startTime.QuadPart);
return elapsed * frequencyMult;
return static_cast<double>(time.QuadPart - startTime.QuadPart) * frequencyMult;
}
// Fake, but usable in a pinch. Don't, though.
@ -91,24 +98,28 @@ void yield() {
YieldProcessor();
}
TimeSpan::TimeSpan() {
Instant::Instant() {
_dbg_assert_(frequencyMult != 0.0);
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&nativeStart_));
}
double TimeSpan::ElapsedSeconds() const {
double Instant::ElapsedSeconds() const {
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
double elapsed = static_cast<double>(time.QuadPart - nativeStart_);
return elapsed * frequencyMult;
}
int64_t TimeSpan::ElapsedNanos() const {
int64_t Instant::ElapsedNanos() const {
return (int64_t)(ElapsedSeconds() * 1000000000.0);
}
#elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
void TimeInit() {
// Nothing to do.
}
// The only intended use is to match the timings in VK_GOOGLE_display_timing
uint64_t time_now_raw() {
struct timespec tp;
@ -174,6 +185,10 @@ double TimeSpan::ElapsedSeconds() const {
#else
void TimeInit() {
// Nothing to do.
}
static time_t start;
double time_now_d() {
@ -229,7 +244,7 @@ int64_t TimeSpan::ElapsedNanos() const {
}
double TimeSpan::ElapsedSeconds() const {
return (double)ElapsedNanos() * (1.0 / 1000000000);
return (double)ElapsedNanos() * (1.0 / 1000000000.0);
}
#endif
@ -246,6 +261,53 @@ void sleep_ms(int ms) {
#endif
}
// Precise Windows sleep function from: https://github.com/blat-blatnik/Snippets/blob/main/precise_sleep.c
// Described in: https://blog.bearcats.nl/perfect-sleep-function/
void sleep_precise(double seconds) {
#ifdef _WIN32
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
INT64 targetQpc = (INT64)(qpc.QuadPart + seconds * QpcPerSecond);
if (Timer) { // Try using a high resolution timer first.
const double TOLERANCE = 0.001'02;
INT64 maxTicks = (INT64)SchedulerPeriodMs * 9'500;
for (;;) // Break sleep up into parts that are lower than scheduler period.
{
double remainingSeconds = (targetQpc - qpc.QuadPart) / (double)QpcPerSecond;
INT64 sleepTicks = (INT64)((remainingSeconds - TOLERANCE) * 10'000'000);
if (sleepTicks <= 0)
break;
LARGE_INTEGER due;
due.QuadPart = -(sleepTicks > maxTicks ? maxTicks : sleepTicks);
SetWaitableTimerEx(Timer, &due, 0, NULL, NULL, NULL, 0);
WaitForSingleObject(Timer, INFINITE);
QueryPerformanceCounter(&qpc);
}
} else { // Fallback to Sleep.
const double TOLERANCE = 0.000'02;
double sleepMs = (seconds - TOLERANCE) * 1000 - SchedulerPeriodMs; // Sleep for 1 scheduler period less than requested.
int sleepSlices = (int)(sleepMs / SchedulerPeriodMs);
if (sleepSlices > 0)
Sleep((DWORD)sleepSlices * SchedulerPeriodMs);
QueryPerformanceCounter(&qpc);
}
while (qpc.QuadPart < targetQpc) // Spin for any remaining time.
{
YieldProcessor();
QueryPerformanceCounter(&qpc);
}
#else
#if defined(HAVE_LIBNX)
svcSleepThread((int64_t)(seconds * 1000000000.0));
#elif defined(__EMSCRIPTEN__)
emscripten_sleep(seconds * 1000.0);
#else
usleep(seconds * 1000000.0);
#endif
#endif
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
// in the form 00:00:000.
void GetCurrentTimeFormatted(char formattedTime[13]) {

View File

@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
void TimeInit();
// Seconds.
double time_now_d();
@ -18,32 +20,24 @@ double time_now_unix_utc();
// Sleep. Does not necessarily have millisecond granularity, especially on Windows.
void sleep_ms(int ms);
// Precise sleep. Can consume a little bit of CPU on Windows at least.
void sleep_precise(double seconds);
// Yield. Signals that this thread is busy-waiting but wants to allow other hyperthreads to run.
void yield();
void GetCurrentTimeFormatted(char formattedTime[13]);
// Rust-style Instant for clear and easy timing.
// Most accurate timer possible - no extra double conversions. Only for spans.
class Instant {
public:
static Instant Now() {
return Instant(time_now_d());
return Instant();
}
double ElapsedSeconds() const {
return time_now_d() - instantTime_;
}
private:
explicit Instant(double initTime) : instantTime_(initTime) {}
double instantTime_;
};
// Most accurate timer possible - no extra double conversions. Only for spans.
class TimeSpan {
public:
TimeSpan();
double ElapsedSeconds() const;
int64_t ElapsedNanos() const;
private:
Instant();
uint64_t nativeStart_;
#ifndef _WIN32
int64_t nsecs_;

View File

@ -285,9 +285,9 @@ void IRJit::RunLoopUntil(u64 globalticks) {
instPtr++;
#ifdef IR_PROFILING
IRBlock *block = blocks_.GetBlock(blocks_.GetBlockNumFromOffset(offset));
TimeSpan span;
Instant start = Instant::Now();
mips->pc = IRInterpret(mips, instPtr);
int64_t elapsedNanos = span.ElapsedNanos();
int64_t elapsedNanos = start.ElapsedNanos();
block->profileStats_.executions += 1;
block->profileStats_.totalNanos += elapsedNanos;
#else

View File

@ -56,7 +56,7 @@
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceKernelMemory.h"
static std::thread loadingThread;
static std::thread g_loadingThread;
static void UseLargeMem(int memsize) {
if (memsize != 1) {
@ -313,7 +313,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {
// Note: this thread reads the game binary, loads caches, and links HLE while UI spins.
// To do something deterministically when the game starts, disabling this thread won't be enough.
// Instead: Use Core_ListenLifecycle() or watch coreState.
loadingThread = std::thread([bootpath] {
g_loadingThread = std::thread([bootpath] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
@ -474,7 +474,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {
PSPLoaders_Shutdown();
// Note: See Load_PSP_ISO for notes about this thread.
loadingThread = std::thread([finalName] {
g_loadingThread = std::thread([finalName] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
@ -500,7 +500,7 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
PSPLoaders_Shutdown();
// Note: See Load_PSP_ISO for notes about this thread.
loadingThread = std::thread([] {
g_loadingThread = std::thread([] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
@ -521,6 +521,6 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
}
void PSPLoaders_Shutdown() {
if (loadingThread.joinable())
loadingThread.join();
if (g_loadingThread.joinable())
g_loadingThread.join();
}

View File

@ -45,6 +45,8 @@
#include "Common/Thread/ThreadUtil.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/HW/Camera.h"
@ -799,6 +801,8 @@ Q_DECL_EXPORT
#endif
int main(int argc, char *argv[])
{
TimeInit();
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--version")) {
printf("%s\n", PPSSPP_GIT_VERSION);

View File

@ -1108,6 +1108,8 @@ int main(int argc, char *argv[]) {
}
}
TimeInit();
#ifdef HAVE_LIBNX
socketInitializeDefault();
nxlinkStdio();

View File

@ -887,6 +887,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
SetCurrentThreadName("Main");
TimeInit();
WinMainInit();
#ifndef _DEBUG

View File

@ -294,6 +294,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
"(Ljava/lang/String;)Ljava/lang/Class;");
RegisterAttachDetach(&Android_AttachThreadToJNI, &Android_DetachThreadFromJNI);
TimeInit();
return JNI_VERSION_1_6;
}

View File

@ -1195,6 +1195,8 @@ static const struct retro_controller_info ports[] =
void retro_init(void)
{
TimeInit();
struct retro_log_callback log;
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
{