Merge pull request #9889 from Morph1984/time-is-ticking
core_timing: Reduce CPU usage on Windows
This commit is contained in:
commit
a7792e5ff8
16 changed files with 324 additions and 65 deletions
|
@ -113,6 +113,8 @@ add_library(common STATIC
|
|||
socket_types.h
|
||||
spin_lock.cpp
|
||||
spin_lock.h
|
||||
steady_clock.cpp
|
||||
steady_clock.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
string_util.cpp
|
||||
|
@ -142,6 +144,14 @@ add_library(common STATIC
|
|||
zstd_compression.h
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_sources(common PRIVATE
|
||||
windows/timer_resolution.cpp
|
||||
windows/timer_resolution.h
|
||||
)
|
||||
target_link_libraries(common PRIVATE ntdll)
|
||||
endif()
|
||||
|
||||
if(ARCHITECTURE_x86_64)
|
||||
target_sources(common
|
||||
PRIVATE
|
||||
|
|
56
src/common/steady_clock.cpp
Normal file
56
src/common/steady_clock.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef _WIN32
|
||||
static s64 WindowsQueryPerformanceFrequency() {
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
return frequency.QuadPart;
|
||||
}
|
||||
|
||||
static s64 WindowsQueryPerformanceCounter() {
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
return counter.QuadPart;
|
||||
}
|
||||
#endif
|
||||
|
||||
SteadyClock::time_point SteadyClock::Now() noexcept {
|
||||
#if defined(_WIN32)
|
||||
static const auto freq = WindowsQueryPerformanceFrequency();
|
||||
const auto counter = WindowsQueryPerformanceCounter();
|
||||
|
||||
// 10 MHz is a very common QPC frequency on modern PCs.
|
||||
// Optimizing for this specific frequency can double the performance of
|
||||
// this function by avoiding the expensive frequency conversion path.
|
||||
static constexpr s64 TenMHz = 10'000'000;
|
||||
|
||||
if (freq == TenMHz) [[likely]] {
|
||||
static_assert(period::den % TenMHz == 0);
|
||||
static constexpr s64 Multiplier = period::den / TenMHz;
|
||||
return time_point{duration{counter * Multiplier}};
|
||||
}
|
||||
|
||||
const auto whole = (counter / freq) * period::den;
|
||||
const auto part = (counter % freq) * period::den / freq;
|
||||
return time_point{duration{whole + part}};
|
||||
#elif defined(__APPLE__)
|
||||
return time_point{duration{clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)}};
|
||||
#else
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return time_point{std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec}};
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace Common
|
23
src/common/steady_clock.h
Normal file
23
src/common/steady_clock.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct SteadyClock {
|
||||
using rep = s64;
|
||||
using period = std::nano;
|
||||
using duration = std::chrono::nanoseconds;
|
||||
using time_point = std::chrono::time_point<SteadyClock>;
|
||||
|
||||
static constexpr bool is_steady = true;
|
||||
|
||||
[[nodiscard]] static time_point Now() noexcept;
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
|
@ -11,45 +12,32 @@
|
|||
|
||||
namespace Common {
|
||||
|
||||
using base_timer = std::chrono::steady_clock;
|
||||
using base_time_point = std::chrono::time_point<base_timer>;
|
||||
|
||||
class StandardWallClock final : public WallClock {
|
||||
public:
|
||||
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
|
||||
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) {
|
||||
start_time = base_timer::now();
|
||||
}
|
||||
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
|
||||
start_time{SteadyClock::Now()} {}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
|
||||
return SteadyClock::Now() - start_time;
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
|
||||
}
|
||||
|
||||
u64 GetClockCycles() override {
|
||||
std::chrono::nanoseconds time_now = GetTimeNS();
|
||||
const u128 temporary =
|
||||
Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temporary, 1000000000).first;
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
}
|
||||
|
||||
u64 GetCPUCycles() override {
|
||||
std::chrono::nanoseconds time_now = GetTimeNS();
|
||||
const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temporary, 1000000000).first;
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
}
|
||||
|
||||
void Pause([[maybe_unused]] bool is_paused) override {
|
||||
|
@ -57,7 +45,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
base_time_point start_time;
|
||||
SteadyClock::time_point start_time;
|
||||
};
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
@ -93,4 +81,9 @@ std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
|||
|
||||
#endif
|
||||
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -55,4 +55,7 @@ private:
|
|||
[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
|
||||
} // namespace Common
|
||||
|
|
109
src/common/windows/timer_resolution.cpp
Normal file
109
src/common/windows/timer_resolution.cpp
Normal file
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "common/windows/timer_resolution.h"
|
||||
|
||||
extern "C" {
|
||||
// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtQueryTimerResolution.html
|
||||
NTSYSAPI LONG NTAPI NtQueryTimerResolution(PULONG MinimumResolution, PULONG MaximumResolution,
|
||||
PULONG CurrentResolution);
|
||||
|
||||
// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
|
||||
NTSYSAPI LONG NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution,
|
||||
PULONG CurrentResolution);
|
||||
|
||||
// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FThread%2FNtDelayExecution.html
|
||||
NTSYSAPI LONG NTAPI NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval);
|
||||
}
|
||||
|
||||
// Defines for compatibility with older Windows 10 SDKs.
|
||||
|
||||
#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
|
||||
#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x1
|
||||
#endif
|
||||
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
|
||||
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4
|
||||
#endif
|
||||
|
||||
namespace Common::Windows {
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
constexpr nanoseconds ToNS(ULONG hundred_ns) {
|
||||
return nanoseconds{hundred_ns * 100};
|
||||
}
|
||||
|
||||
constexpr ULONG ToHundredNS(nanoseconds ns) {
|
||||
return static_cast<ULONG>(ns.count()) / 100;
|
||||
}
|
||||
|
||||
struct TimerResolution {
|
||||
std::chrono::nanoseconds minimum;
|
||||
std::chrono::nanoseconds maximum;
|
||||
std::chrono::nanoseconds current;
|
||||
};
|
||||
|
||||
TimerResolution GetTimerResolution() {
|
||||
ULONG MinimumTimerResolution;
|
||||
ULONG MaximumTimerResolution;
|
||||
ULONG CurrentTimerResolution;
|
||||
NtQueryTimerResolution(&MinimumTimerResolution, &MaximumTimerResolution,
|
||||
&CurrentTimerResolution);
|
||||
return {
|
||||
.minimum{ToNS(MinimumTimerResolution)},
|
||||
.maximum{ToNS(MaximumTimerResolution)},
|
||||
.current{ToNS(CurrentTimerResolution)},
|
||||
};
|
||||
}
|
||||
|
||||
void SetHighQoS() {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service
|
||||
PROCESS_POWER_THROTTLING_STATE PowerThrottling{
|
||||
.Version{PROCESS_POWER_THROTTLING_CURRENT_VERSION},
|
||||
.ControlMask{PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
|
||||
PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION},
|
||||
.StateMask{},
|
||||
};
|
||||
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &PowerThrottling,
|
||||
sizeof(PROCESS_POWER_THROTTLING_STATE));
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
nanoseconds GetMinimumTimerResolution() {
|
||||
return GetTimerResolution().minimum;
|
||||
}
|
||||
|
||||
nanoseconds GetMaximumTimerResolution() {
|
||||
return GetTimerResolution().maximum;
|
||||
}
|
||||
|
||||
nanoseconds GetCurrentTimerResolution() {
|
||||
return GetTimerResolution().current;
|
||||
}
|
||||
|
||||
nanoseconds SetCurrentTimerResolution(nanoseconds timer_resolution) {
|
||||
// Set the timer resolution, and return the current timer resolution.
|
||||
const auto DesiredTimerResolution = ToHundredNS(timer_resolution);
|
||||
ULONG CurrentTimerResolution;
|
||||
NtSetTimerResolution(DesiredTimerResolution, TRUE, &CurrentTimerResolution);
|
||||
return ToNS(CurrentTimerResolution);
|
||||
}
|
||||
|
||||
nanoseconds SetCurrentTimerResolutionToMaximum() {
|
||||
SetHighQoS();
|
||||
return SetCurrentTimerResolution(GetMaximumTimerResolution());
|
||||
}
|
||||
|
||||
void SleepForOneTick() {
|
||||
LARGE_INTEGER DelayInterval{
|
||||
.QuadPart{-1},
|
||||
};
|
||||
NtDelayExecution(FALSE, &DelayInterval);
|
||||
}
|
||||
|
||||
} // namespace Common::Windows
|
38
src/common/windows/timer_resolution.h
Normal file
38
src/common/windows/timer_resolution.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Common::Windows {
|
||||
|
||||
/// Returns the minimum (least precise) supported timer resolution in nanoseconds.
|
||||
std::chrono::nanoseconds GetMinimumTimerResolution();
|
||||
|
||||
/// Returns the maximum (most precise) supported timer resolution in nanoseconds.
|
||||
std::chrono::nanoseconds GetMaximumTimerResolution();
|
||||
|
||||
/// Returns the current timer resolution in nanoseconds.
|
||||
std::chrono::nanoseconds GetCurrentTimerResolution();
|
||||
|
||||
/**
|
||||
* Sets the current timer resolution.
|
||||
*
|
||||
* @param timer_resolution Timer resolution in nanoseconds.
|
||||
*
|
||||
* @returns The current timer resolution.
|
||||
*/
|
||||
std::chrono::nanoseconds SetCurrentTimerResolution(std::chrono::nanoseconds timer_resolution);
|
||||
|
||||
/**
|
||||
* Sets the current timer resolution to the maximum supported timer resolution.
|
||||
*
|
||||
* @returns The current timer resolution.
|
||||
*/
|
||||
std::chrono::nanoseconds SetCurrentTimerResolutionToMaximum();
|
||||
|
||||
/// Sleep for one tick of the current timer resolution.
|
||||
void SleepForOneTick();
|
||||
|
||||
} // namespace Common::Windows
|
|
@ -6,6 +6,7 @@
|
|||
#include <thread>
|
||||
|
||||
#include "common/atomic_ops.h"
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
|
||||
|
@ -39,6 +40,12 @@ static u64 FencedRDTSC() {
|
|||
}
|
||||
#endif
|
||||
|
||||
template <u64 Nearest>
|
||||
static u64 RoundToNearest(u64 value) {
|
||||
const auto mod = value % Nearest;
|
||||
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
|
||||
}
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
FencedRDTSC();
|
||||
|
@ -46,18 +53,18 @@ u64 EstimateRDTSCFrequency() {
|
|||
FencedRDTSC();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
const auto start_time = Common::SteadyClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 200 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{200});
|
||||
const auto end_time = std::chrono::steady_clock::now();
|
||||
// Wait for 250 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{250});
|
||||
const auto end_time = Common::SteadyClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return tsc_freq;
|
||||
return RoundToNearest<1000>(tsc_freq);
|
||||
}
|
||||
|
||||
namespace X64 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue