10 changed files with 378 additions and 40 deletions
-
4src/common/CMakeLists.txt
-
90src/common/wall_clock.cpp
-
40src/common/wall_clock.h
-
33src/common/x64/cpu_detect.cpp
-
12src/common/x64/cpu_detect.h
-
128src/common/x64/native_clock.cpp
-
41src/common/x64/native_clock.h
-
21src/core/host_timing.cpp
-
4src/core/host_timing.h
-
45src/tests/core/host_timing.cpp
@ -0,0 +1,90 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/uint128.h"
|
|||
#include "common/wall_clock.h"
|
|||
|
|||
#ifdef ARCHITECTURE_x86_64
|
|||
#include "common/x64/cpu_detect.h"
|
|||
#include "common/x64/native_clock.h"
|
|||
#endif
|
|||
|
|||
namespace Common { |
|||
|
|||
using base_timer = std::chrono::steady_clock; |
|||
using base_time_point = std::chrono::time_point<base_timer>; |
|||
|
|||
class StandardWallClock : public WallClock { |
|||
public: |
|||
StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency) |
|||
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) { |
|||
start_time = base_timer::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); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
u64 GetClockCycles() override { |
|||
std::chrono::nanoseconds time_now = GetTimeNS(); |
|||
const u128 temporal = Common::Multiply64Into128(time_now.count(), emulated_clock_frequency); |
|||
return Common::Divide128On32(temporal, 1000000000).first; |
|||
} |
|||
|
|||
u64 GetCPUCycles() override { |
|||
std::chrono::nanoseconds time_now = GetTimeNS(); |
|||
const u128 temporal = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency); |
|||
return Common::Divide128On32(temporal, 1000000000).first; |
|||
} |
|||
|
|||
private: |
|||
base_time_point start_time; |
|||
}; |
|||
|
|||
#ifdef ARCHITECTURE_x86_64
|
|||
|
|||
WallClock* CreateBestMatchingClock(u32 emulated_cpu_frequency, u32 emulated_clock_frequency) { |
|||
const auto& caps = GetCPUCaps(); |
|||
u64 rtsc_frequency = 0; |
|||
if (caps.invariant_tsc) { |
|||
if (caps.base_frequency != 0) { |
|||
rtsc_frequency = static_cast<u64>(caps.base_frequency) * 1000000U; |
|||
} |
|||
if (rtsc_frequency == 0) { |
|||
rtsc_frequency = EstimateRDTSCFrequency(); |
|||
} |
|||
} |
|||
if (rtsc_frequency == 0) { |
|||
return static_cast<WallClock*>( |
|||
new StandardWallClock(emulated_cpu_frequency, emulated_clock_frequency)); |
|||
} else { |
|||
return static_cast<WallClock*>( |
|||
new X64::NativeClock(emulated_cpu_frequency, emulated_clock_frequency, rtsc_frequency)); |
|||
} |
|||
} |
|||
|
|||
#else
|
|||
|
|||
WallClock* CreateBestMatchingClock(u32 emulated_cpu_frequency, u32 emulated_clock_frequency) { |
|||
return static_cast<WallClock*>( |
|||
new StandardWallClock(emulated_cpu_frequency, emulated_clock_frequency)); |
|||
} |
|||
|
|||
#endif
|
|||
|
|||
} // namespace Common
|
|||
@ -0,0 +1,40 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Common { |
|||
|
|||
class WallClock { |
|||
public: |
|||
virtual std::chrono::nanoseconds GetTimeNS() = 0; |
|||
virtual std::chrono::microseconds GetTimeUS() = 0; |
|||
virtual std::chrono::milliseconds GetTimeMS() = 0; |
|||
virtual u64 GetClockCycles() = 0; |
|||
virtual u64 GetCPUCycles() = 0; |
|||
|
|||
/// Tells if the wall clock, uses the host CPU's hardware clock |
|||
bool IsNative() const { |
|||
return is_native; |
|||
} |
|||
|
|||
protected: |
|||
WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native) |
|||
: emulated_cpu_frequency{emulated_cpu_frequency}, |
|||
emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {} |
|||
|
|||
u64 emulated_cpu_frequency; |
|||
u64 emulated_clock_frequency; |
|||
|
|||
private: |
|||
bool is_native; |
|||
}; |
|||
|
|||
WallClock* CreateBestMatchingClock(u32 emulated_cpu_frequency, u32 emulated_clock_frequency); |
|||
|
|||
} // namespace Common |
|||
@ -0,0 +1,128 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <chrono>
|
|||
#include <thread>
|
|||
|
|||
#ifdef _MSC_VER
|
|||
#include <intrin.h>
|
|||
#else
|
|||
#include <x86intrin.h>
|
|||
#endif
|
|||
|
|||
#include "common/x64/native_clock.h"
|
|||
|
|||
namespace Common { |
|||
|
|||
#ifdef _MSC_VER
|
|||
|
|||
namespace { |
|||
|
|||
struct uint128 { |
|||
u64 low; |
|||
u64 high; |
|||
}; |
|||
|
|||
u64 umuldiv64(u64 a, u64 b, u64 d) { |
|||
uint128 r{}; |
|||
r.low = _umul128(a, b, &r.high); |
|||
u64 remainder; |
|||
return _udiv128(r.high, r.low, d, &remainder); |
|||
} |
|||
|
|||
} // namespace
|
|||
|
|||
#else
|
|||
|
|||
namespace { |
|||
|
|||
u64 umuldiv64(u64 a, u64 b, u64 d) { |
|||
const u64 diva = a / d; |
|||
const u64 moda = a % d; |
|||
const u64 divb = b / d; |
|||
const u64 modb = b % d; |
|||
return diva * b + moda * divb + moda * modb / d; |
|||
} |
|||
|
|||
} // namespace
|
|||
|
|||
#endif
|
|||
|
|||
u64 EstimateRDTSCFrequency() { |
|||
const auto milli_10 = std::chrono::milliseconds{10}; |
|||
// get current time
|
|||
_mm_mfence(); |
|||
const u64 tscStart = __rdtsc(); |
|||
const auto startTime = std::chrono::high_resolution_clock::now(); |
|||
// wait roughly 3 seconds
|
|||
while (true) { |
|||
auto milli = std::chrono::duration_cast<std::chrono::milliseconds>( |
|||
std::chrono::high_resolution_clock::now() - startTime); |
|||
if (milli.count() >= 3000) |
|||
break; |
|||
std::this_thread::sleep_for(milli_10); |
|||
} |
|||
const auto endTime = std::chrono::high_resolution_clock::now(); |
|||
_mm_mfence(); |
|||
const u64 tscEnd = __rdtsc(); |
|||
// calculate difference
|
|||
const u64 timer_diff = |
|||
std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count(); |
|||
const u64 tsc_diff = tscEnd - tscStart; |
|||
const u64 tsc_freq = umuldiv64(tsc_diff, 1000000000ULL, timer_diff); |
|||
return tsc_freq; |
|||
} |
|||
|
|||
namespace X64 { |
|||
NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, |
|||
u64 rtsc_frequency) |
|||
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{ |
|||
rtsc_frequency} { |
|||
_mm_mfence(); |
|||
last_measure = __rdtsc(); |
|||
accumulated_ticks = 0U; |
|||
} |
|||
|
|||
u64 NativeClock::GetRTSC() { |
|||
rtsc_serialize.lock(); |
|||
_mm_mfence(); |
|||
const u64 current_measure = __rdtsc(); |
|||
u64 diff = current_measure - last_measure; |
|||
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
|
|||
if (current_measure > last_measure) { |
|||
last_measure = current_measure; |
|||
} |
|||
accumulated_ticks += diff; |
|||
rtsc_serialize.unlock(); |
|||
return accumulated_ticks; |
|||
} |
|||
|
|||
std::chrono::nanoseconds NativeClock::GetTimeNS() { |
|||
const u64 rtsc_value = GetRTSC(); |
|||
return std::chrono::nanoseconds{umuldiv64(rtsc_value, 1000000000, rtsc_frequency)}; |
|||
} |
|||
|
|||
std::chrono::microseconds NativeClock::GetTimeUS() { |
|||
const u64 rtsc_value = GetRTSC(); |
|||
return std::chrono::microseconds{umuldiv64(rtsc_value, 1000000, rtsc_frequency)}; |
|||
} |
|||
|
|||
std::chrono::milliseconds NativeClock::GetTimeMS() { |
|||
const u64 rtsc_value = GetRTSC(); |
|||
return std::chrono::milliseconds{umuldiv64(rtsc_value, 1000, rtsc_frequency)}; |
|||
} |
|||
|
|||
u64 NativeClock::GetClockCycles() { |
|||
const u64 rtsc_value = GetRTSC(); |
|||
return umuldiv64(rtsc_value, emulated_clock_frequency, rtsc_frequency); |
|||
} |
|||
|
|||
u64 NativeClock::GetCPUCycles() { |
|||
const u64 rtsc_value = GetRTSC(); |
|||
return umuldiv64(rtsc_value, emulated_cpu_frequency, rtsc_frequency); |
|||
} |
|||
|
|||
} // namespace X64
|
|||
|
|||
} // namespace Common
|
|||
@ -0,0 +1,41 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <optional> |
|||
|
|||
#include "common/spin_lock.h" |
|||
#include "common/wall_clock.h" |
|||
|
|||
namespace Common { |
|||
|
|||
namespace X64 { |
|||
class NativeClock : public WallClock { |
|||
public: |
|||
NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency); |
|||
|
|||
std::chrono::nanoseconds GetTimeNS() override; |
|||
|
|||
std::chrono::microseconds GetTimeUS() override; |
|||
|
|||
std::chrono::milliseconds GetTimeMS() override; |
|||
|
|||
u64 GetClockCycles() override; |
|||
|
|||
u64 GetCPUCycles() override; |
|||
|
|||
private: |
|||
u64 GetRTSC(); |
|||
|
|||
SpinLock rtsc_serialize{}; |
|||
u64 last_measure{}; |
|||
u64 accumulated_ticks{}; |
|||
u64 rtsc_frequency; |
|||
}; |
|||
} // namespace X64 |
|||
|
|||
u64 EstimateRDTSCFrequency(); |
|||
|
|||
} // namespace Common |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue