mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-03 14:49:22 +00:00
Allow PerformanceTrackers to track abstract times
This commit is contained in:
parent
0b9b09ad32
commit
061ce71a6b
4 changed files with 89 additions and 55 deletions
|
@ -20,10 +20,9 @@ void PerformanceMetrics::Reset()
|
||||||
m_fps_counter.Reset();
|
m_fps_counter.Reset();
|
||||||
m_vps_counter.Reset();
|
m_vps_counter.Reset();
|
||||||
m_speed_counter.Reset();
|
m_speed_counter.Reset();
|
||||||
|
m_max_speed_counter.Reset();
|
||||||
|
|
||||||
m_time_sleeping = DT::zero();
|
m_prev_adjusted_time = Clock::now() - m_time_sleeping;
|
||||||
m_real_times.fill(Clock::now());
|
|
||||||
m_cpu_times.fill(Core::System::GetInstance().GetCoreTiming().GetCPUTimePoint(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceMetrics::CountFrame()
|
void PerformanceMetrics::CountFrame()
|
||||||
|
@ -45,11 +44,10 @@ void PerformanceMetrics::CountThrottleSleep(DT sleep)
|
||||||
void PerformanceMetrics::CountPerformanceMarker(Core::System& system, s64 cyclesLate)
|
void PerformanceMetrics::CountPerformanceMarker(Core::System& system, s64 cyclesLate)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(m_time_lock);
|
std::unique_lock lock(m_time_lock);
|
||||||
|
const TimePoint adjusted_time = Clock::now() - m_time_sleeping;
|
||||||
m_speed_counter.Count();
|
m_speed_counter.Count();
|
||||||
|
m_max_speed_counter.Count(adjusted_time - m_prev_adjusted_time);
|
||||||
m_real_times[m_time_index] = Clock::now() - m_time_sleeping;
|
m_prev_adjusted_time = adjusted_time;
|
||||||
m_cpu_times[m_time_index] = system.GetCoreTiming().GetCPUTimePoint(cyclesLate);
|
|
||||||
m_time_index += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double PerformanceMetrics::GetFPS() const
|
double PerformanceMetrics::GetFPS() const
|
||||||
|
@ -69,9 +67,7 @@ double PerformanceMetrics::GetSpeed() const
|
||||||
|
|
||||||
double PerformanceMetrics::GetMaxSpeed() const
|
double PerformanceMetrics::GetMaxSpeed() const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(m_time_lock);
|
return m_max_speed_counter.GetHzAvg() / 100.0;
|
||||||
return DT_s(m_cpu_times[u8(m_time_index - 1)] - m_cpu_times[m_time_index]) /
|
|
||||||
DT_s(m_real_times[u8(m_time_index - 1)] - m_real_times[m_time_index]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double PerformanceMetrics::GetLastSpeedDenominator() const
|
double PerformanceMetrics::GetLastSpeedDenominator() const
|
||||||
|
|
|
@ -47,15 +47,13 @@ public:
|
||||||
private:
|
private:
|
||||||
PerformanceTracker m_fps_counter{"render_times.txt"};
|
PerformanceTracker m_fps_counter{"render_times.txt"};
|
||||||
PerformanceTracker m_vps_counter{"vblank_times.txt"};
|
PerformanceTracker m_vps_counter{"vblank_times.txt"};
|
||||||
PerformanceTracker m_speed_counter{std::nullopt, 1000000};
|
PerformanceTracker m_speed_counter{std::nullopt, 1280000};
|
||||||
|
PerformanceTracker m_max_speed_counter{std::nullopt, 1280000};
|
||||||
|
|
||||||
double m_graph_max_time = 0.0;
|
double m_graph_max_time = 0.0;
|
||||||
|
|
||||||
mutable std::shared_mutex m_time_lock;
|
mutable std::shared_mutex m_time_lock;
|
||||||
|
TimePoint m_prev_adjusted_time{};
|
||||||
u8 m_time_index = 0;
|
|
||||||
std::array<TimePoint, 256> m_real_times{};
|
|
||||||
std::array<TimePoint, 256> m_cpu_times{};
|
|
||||||
DT m_time_sleeping{};
|
DT m_time_sleeping{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
static constexpr double SAMPLE_RC_RATIO = 0.25;
|
static constexpr double SAMPLE_RC_RATIO = 0.33;
|
||||||
|
|
||||||
PerformanceTracker::PerformanceTracker(const std::optional<std::string> log_name,
|
PerformanceTracker::PerformanceTracker(const std::optional<std::string> log_name,
|
||||||
const std::optional<s64> sample_window_us)
|
const std::optional<s64> sample_window_us)
|
||||||
|
@ -47,7 +47,7 @@ void PerformanceTracker::Reset()
|
||||||
m_dt_std = std::nullopt;
|
m_dt_std = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceTracker::Count()
|
void PerformanceTracker::Count(std::optional<DT> custom_value, bool value_is_duration)
|
||||||
{
|
{
|
||||||
std::unique_lock lock{m_mutex};
|
std::unique_lock lock{m_mutex};
|
||||||
|
|
||||||
|
@ -57,26 +57,31 @@ void PerformanceTracker::Count()
|
||||||
const DT window{GetSampleWindow()};
|
const DT window{GetSampleWindow()};
|
||||||
|
|
||||||
const TimePoint time{Clock::now()};
|
const TimePoint time{Clock::now()};
|
||||||
const DT diff{time - m_last_time};
|
const DT duration{time - m_last_time};
|
||||||
|
const DT value{custom_value.value_or(duration)};
|
||||||
|
const TimeDataPair data_point{value_is_duration ? value : duration, value};
|
||||||
m_last_time = time;
|
m_last_time = time;
|
||||||
|
|
||||||
QueuePush(diff);
|
QueuePush(data_point);
|
||||||
m_dt_total += diff;
|
m_dt_total += data_point;
|
||||||
|
|
||||||
if (m_dt_queue_begin == m_dt_queue_end)
|
if (m_dt_queue_begin == m_dt_queue_end)
|
||||||
m_dt_total -= QueuePop();
|
m_dt_total -= QueuePop();
|
||||||
|
|
||||||
while (window <= m_dt_total - QueueTop())
|
while (window <= m_dt_total.duration - QueueTop().duration)
|
||||||
m_dt_total -= QueuePop();
|
m_dt_total -= QueuePop();
|
||||||
|
|
||||||
// Simple Moving Average Throughout the Window
|
// Simple Moving Average Throughout the Window
|
||||||
m_dt_avg = m_dt_total / QueueSize();
|
// We want the average value, so we use the value
|
||||||
const double hz = DT_s(1.0) / m_dt_avg;
|
m_dt_avg = m_dt_total.value / QueueSize();
|
||||||
|
|
||||||
|
// Even though the frequency does not make sense if the value
|
||||||
|
// is not the duration, it is still useful to have the value
|
||||||
|
const double hz = DT_s(QueueSize()) / m_dt_total.value;
|
||||||
|
|
||||||
// Exponential Moving Average
|
// Exponential Moving Average
|
||||||
const DT_s rc = SAMPLE_RC_RATIO * std::min(window, m_dt_total);
|
const DT_s rc = SAMPLE_RC_RATIO * window;
|
||||||
const double a = 1.0 - std::exp(-(DT_s(diff) / rc));
|
const double a = 1.0 - std::exp(-(DT_s(data_point.duration) / rc));
|
||||||
|
|
||||||
// Sometimes euler averages can break when the average is inf/nan
|
// Sometimes euler averages can break when the average is inf/nan
|
||||||
if (std::isfinite(m_hz_avg))
|
if (std::isfinite(m_hz_avg))
|
||||||
|
@ -86,7 +91,7 @@ void PerformanceTracker::Count()
|
||||||
|
|
||||||
m_dt_std = std::nullopt;
|
m_dt_std = std::nullopt;
|
||||||
|
|
||||||
LogRenderTimeToFile(diff);
|
LogRenderTimeToFile(data_point.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DT PerformanceTracker::GetSampleWindow() const
|
DT PerformanceTracker::GetSampleWindow() const
|
||||||
|
@ -121,7 +126,7 @@ DT PerformanceTracker::GetDtStd() const
|
||||||
double total = 0.0;
|
double total = 0.0;
|
||||||
for (std::size_t i = m_dt_queue_begin; i != m_dt_queue_end; i = IncrementIndex(i))
|
for (std::size_t i = m_dt_queue_begin; i != m_dt_queue_end; i = IncrementIndex(i))
|
||||||
{
|
{
|
||||||
double diff = DT_s(m_dt_queue[i] - m_dt_avg).count();
|
double diff = DT_s(m_dt_queue[i].value - m_dt_avg).count();
|
||||||
total += diff * diff;
|
total += diff * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +141,7 @@ DT PerformanceTracker::GetLastRawDt() const
|
||||||
if (QueueEmpty())
|
if (QueueEmpty())
|
||||||
return DT::zero();
|
return DT::zero();
|
||||||
|
|
||||||
return QueueBottom();
|
return QueueBottom().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceTracker::ImPlotPlotLines(const char* label) const
|
void PerformanceTracker::ImPlotPlotLines(const char* label) const
|
||||||
|
@ -152,35 +157,32 @@ void PerformanceTracker::ImPlotPlotLines(const char* label) const
|
||||||
const bool quality = QueueSize() < MAX_QUALITY_GRAPH_SIZE;
|
const bool quality = QueueSize() < MAX_QUALITY_GRAPH_SIZE;
|
||||||
|
|
||||||
const DT update_time = Clock::now() - m_last_time;
|
const DT update_time = Clock::now() - m_last_time;
|
||||||
const float predicted_frame_time = DT_ms(std::max(update_time, QueueBottom())).count();
|
|
||||||
|
|
||||||
std::size_t points = 0;
|
std::size_t points = 0;
|
||||||
if (quality)
|
x[points] = 0.f;
|
||||||
{
|
y[points] = DT_ms(QueueBottom().value).count();
|
||||||
x[points] = 0.f;
|
++points;
|
||||||
y[points] = predicted_frame_time;
|
|
||||||
++points;
|
|
||||||
}
|
|
||||||
|
|
||||||
x[points] = DT_ms(update_time).count();
|
x[points] = DT_ms(update_time).count();
|
||||||
y[points] = predicted_frame_time;
|
y[points] = y[points - 1];
|
||||||
++points;
|
++points;
|
||||||
|
|
||||||
const std::size_t begin = DecrementIndex(m_dt_queue_end);
|
const std::size_t begin = DecrementIndex(m_dt_queue_end);
|
||||||
const std::size_t end = DecrementIndex(m_dt_queue_begin);
|
const std::size_t end = DecrementIndex(m_dt_queue_begin);
|
||||||
for (std::size_t i = begin; i != end; i = DecrementIndex(i))
|
for (std::size_t i = begin; i != end; i = DecrementIndex(i))
|
||||||
{
|
{
|
||||||
const float frame_time_ms = DT_ms(m_dt_queue[i]).count();
|
const float frame_duration_ms = DT_ms(m_dt_queue[i].duration).count();
|
||||||
|
const float frame_value_ms = DT_ms(m_dt_queue[i].value).count();
|
||||||
|
|
||||||
if (quality)
|
if (quality)
|
||||||
{
|
{
|
||||||
x[points] = x[points - 1];
|
x[points] = x[points - 1];
|
||||||
y[points] = frame_time_ms;
|
y[points] = frame_value_ms;
|
||||||
++points;
|
++points;
|
||||||
}
|
}
|
||||||
|
|
||||||
x[points] = x[points - 1] + frame_time_ms;
|
x[points] = x[points - 1] + frame_duration_ms;
|
||||||
y[points] = frame_time_ms;
|
y[points] = frame_value_ms;
|
||||||
++points;
|
++points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,25 +196,25 @@ void PerformanceTracker::QueueClear()
|
||||||
m_dt_queue_end = 0;
|
m_dt_queue_end = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceTracker::QueuePush(DT dt)
|
void PerformanceTracker::QueuePush(TimeDataPair dt)
|
||||||
{
|
{
|
||||||
m_dt_queue[m_dt_queue_end] = dt;
|
m_dt_queue[m_dt_queue_end] = dt;
|
||||||
m_dt_queue_end = IncrementIndex(m_dt_queue_end);
|
m_dt_queue_end = IncrementIndex(m_dt_queue_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DT& PerformanceTracker::QueuePop()
|
const PerformanceTracker::TimeDataPair& PerformanceTracker::QueuePop()
|
||||||
{
|
{
|
||||||
const std::size_t top = m_dt_queue_begin;
|
const std::size_t top = m_dt_queue_begin;
|
||||||
m_dt_queue_begin = IncrementIndex(m_dt_queue_begin);
|
m_dt_queue_begin = IncrementIndex(m_dt_queue_begin);
|
||||||
return m_dt_queue[top];
|
return m_dt_queue[top];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DT& PerformanceTracker::QueueTop() const
|
const PerformanceTracker::TimeDataPair& PerformanceTracker::QueueTop() const
|
||||||
{
|
{
|
||||||
return m_dt_queue[m_dt_queue_begin];
|
return m_dt_queue[m_dt_queue_begin];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DT& PerformanceTracker::QueueBottom() const
|
const PerformanceTracker::TimeDataPair& PerformanceTracker::QueueBottom() const
|
||||||
{
|
{
|
||||||
return m_dt_queue[DecrementIndex(m_dt_queue_end)];
|
return m_dt_queue[DecrementIndex(m_dt_queue_end)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ class PerformanceTracker
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// Must be powers of 2 for masking to work
|
// Must be powers of 2 for masking to work
|
||||||
static constexpr u64 MAX_DT_QUEUE_SIZE = 1UL << 12;
|
static constexpr u64 MAX_DT_QUEUE_SIZE = 1UL << 13;
|
||||||
static constexpr u64 MAX_QUALITY_GRAPH_SIZE = 1UL << 8;
|
static constexpr u64 MAX_QUALITY_GRAPH_SIZE = 1UL << 8;
|
||||||
|
|
||||||
static inline std::size_t IncrementIndex(const std::size_t index)
|
static inline std::size_t IncrementIndex(const std::size_t index)
|
||||||
|
@ -33,6 +33,31 @@ private:
|
||||||
return (end - begin) & (MAX_DT_QUEUE_SIZE - 1);
|
return (end - begin) & (MAX_DT_QUEUE_SIZE - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TimeDataPair
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TimeDataPair(DT duration, DT value) : duration{duration}, value{value} {}
|
||||||
|
TimeDataPair(DT duration) : TimeDataPair{duration, duration} {}
|
||||||
|
TimeDataPair() : TimeDataPair{DT::zero()} {}
|
||||||
|
|
||||||
|
TimeDataPair& operator+=(const TimeDataPair& other)
|
||||||
|
{
|
||||||
|
duration += other.duration;
|
||||||
|
value += other.value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeDataPair& operator-=(const TimeDataPair& other)
|
||||||
|
{
|
||||||
|
duration -= other.duration;
|
||||||
|
value -= other.value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
DT duration, value;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PerformanceTracker(const std::optional<std::string> log_name = std::nullopt,
|
PerformanceTracker(const std::optional<std::string> log_name = std::nullopt,
|
||||||
const std::optional<s64> sample_window_us = std::nullopt);
|
const std::optional<s64> sample_window_us = std::nullopt);
|
||||||
|
@ -45,7 +70,20 @@ public:
|
||||||
|
|
||||||
// Functions for recording performance information
|
// Functions for recording performance information
|
||||||
void Reset();
|
void Reset();
|
||||||
void Count();
|
|
||||||
|
/**
|
||||||
|
* custom_value can be used if you are recording something with it's own DT. For example,
|
||||||
|
* if you are recording the fallback of the throttler or the latency of the frame.
|
||||||
|
*
|
||||||
|
* If a custom_value is not supplied, the value will be set to the time between calls aka,
|
||||||
|
* duration. This is the most common use case of this class, as an FPS counter.
|
||||||
|
*
|
||||||
|
* The boolean value_is_duration should be set to true if the custom DTs you are providing
|
||||||
|
* represent a continuous duration. For example, the present times from a render backend
|
||||||
|
* would set value_is_duration to true. Things like throttler fallback or frame latency
|
||||||
|
* are not continuous, so they should not represent duration.
|
||||||
|
*/
|
||||||
|
void Count(std::optional<DT> custom_value = std::nullopt, bool value_is_duration = false);
|
||||||
|
|
||||||
// Functions for reading performance information
|
// Functions for reading performance information
|
||||||
DT GetSampleWindow() const;
|
DT GetSampleWindow() const;
|
||||||
|
@ -61,13 +99,13 @@ public:
|
||||||
|
|
||||||
private: // Functions for managing dt queue
|
private: // Functions for managing dt queue
|
||||||
inline void QueueClear();
|
inline void QueueClear();
|
||||||
inline void QueuePush(DT dt);
|
inline void QueuePush(TimeDataPair dt);
|
||||||
inline const DT& QueuePop();
|
inline const TimeDataPair& QueuePop();
|
||||||
inline const DT& QueueTop() const;
|
inline const TimeDataPair& QueueTop() const;
|
||||||
inline const DT& QueueBottom() const;
|
inline const TimeDataPair& QueueBottom() const;
|
||||||
|
|
||||||
std::size_t inline QueueSize() const;
|
inline std::size_t QueueSize() const;
|
||||||
bool inline QueueEmpty() const;
|
inline bool QueueEmpty() const;
|
||||||
|
|
||||||
// Handle pausing and logging
|
// Handle pausing and logging
|
||||||
void LogRenderTimeToFile(DT val);
|
void LogRenderTimeToFile(DT val);
|
||||||
|
@ -87,8 +125,8 @@ private: // Functions for managing dt queue
|
||||||
const std::optional<s64> m_sample_window_us;
|
const std::optional<s64> m_sample_window_us;
|
||||||
|
|
||||||
// Queue + Running Total used to calculate average dt
|
// Queue + Running Total used to calculate average dt
|
||||||
DT m_dt_total = DT::zero();
|
TimeDataPair m_dt_total;
|
||||||
std::array<DT, MAX_DT_QUEUE_SIZE> m_dt_queue;
|
std::array<TimeDataPair, MAX_DT_QUEUE_SIZE> m_dt_queue;
|
||||||
std::size_t m_dt_queue_begin = 0;
|
std::size_t m_dt_queue_begin = 0;
|
||||||
std::size_t m_dt_queue_end = 0;
|
std::size_t m_dt_queue_end = 0;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue