diff options
| author | Martino Fontana <tinozzo123@gmail.com> | 2026-09-15 18:20:05 +0200 |
|---|---|---|
| committer | Martino Fontana <tinozzo123@gmail.com> | 2026-10-31 22:52:44 +0100 |
| commit | c6e63fb3ab481ec2b5fd41a8ea8ab9d7597250e1 (patch) | |
| tree | d890bda626c78873dad7c11fc539152d11340814 /Source | |
| parent | 613db73aeb2c086a816592df2fb193d73130101b (diff) | |
PerformanceTracker: Small cleanup, properly use RunningVariance for the standard deviation
UpdateStats() was calculating the standard deviation from the entirety of m_dt_queue, which has a noticeable performance impact when the frame rates are very high.
On a scene so light it runs at 3000 FPS, UpdateStats() was taking around 5% of the profiling (at default 1s sample window). Now it doesn't.
Diffstat (limited to 'Source')
| -rw-r--r-- | Source/Core/Common/MathUtil.h | 23 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/PerformanceTracker.cpp | 92 | ||||
| -rw-r--r-- | Source/Core/VideoCommon/PerformanceTracker.h | 9 |
3 files changed, 73 insertions, 51 deletions
diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h index 47440f2239..b96e8f55fe 100644 --- a/Source/Core/Common/MathUtil.h +++ b/Source/Core/Common/MathUtil.h @@ -134,9 +134,19 @@ public: constexpr void Clear() { *this = {}; } constexpr void Push(T x) { m_mean = m_mean + (x - m_mean) / ++m_count; } + constexpr void Pop(T x) + { + if (m_count <= 1) + { + Clear(); + return; + } + m_mean = m_mean - (x - m_mean) / --m_count; + } constexpr size_t Count() const { return m_count; } constexpr T Mean() const { return m_mean; } + constexpr T Total() const { return m_mean * m_count; } private: size_t m_count = 0; @@ -155,9 +165,22 @@ public: m_running_mean.Push(x); m_variance += (x - old_mean) * (x - m_running_mean.Mean()); } + constexpr void Pop(T x) + { + if (m_running_mean.Count() <= 1) + { + Clear(); + return; + } + + const T old_mean = m_running_mean.Mean(); + m_running_mean.Pop(x); + m_variance -= (x - old_mean) * (x - m_running_mean.Mean()); + } constexpr size_t Count() const { return m_running_mean.Count(); } constexpr T Mean() const { return m_running_mean.Mean(); } + constexpr T Total() const { return m_running_mean.Total(); } constexpr T Variance() const { return m_variance / (Count() - 1); } T StandardDeviation() const { return std::sqrt(Variance()); } diff --git a/Source/Core/VideoCommon/PerformanceTracker.cpp b/Source/Core/VideoCommon/PerformanceTracker.cpp index 2ff6a7863b..a70d7607a0 100644 --- a/Source/Core/VideoCommon/PerformanceTracker.cpp +++ b/Source/Core/VideoCommon/PerformanceTracker.cpp @@ -31,13 +31,13 @@ void PerformanceTracker::Reset() m_raw_dts.Clear(); m_dt_queue.clear(); - m_dt_total = DT::zero(); - m_last_raw_dt = DT::zero(); + m_running_variance.Clear(); + m_last_raw_dt.store(DT::zero(), std::memory_order_relaxed); m_last_time = Clock::now(); - m_hz_avg = 0.0; - m_dt_avg = DT::zero(); - m_dt_std = DT::zero(); - m_is_last_time_sane = false; + m_hz_avg.store(0.0, std::memory_order_relaxed); + m_dt_avg.store(DT::zero(), std::memory_order_relaxed); + m_dt_std.store(DT::zero(), std::memory_order_relaxed); + m_is_last_time_sane.store(false, std::memory_order_relaxed); } void PerformanceTracker::Count() @@ -47,58 +47,54 @@ void PerformanceTracker::Count() const DT diff{current_time - m_last_time}; m_last_time = current_time; - if (!m_is_last_time_sane) + if (!m_is_last_time_sane.load(std::memory_order_relaxed)) { - m_is_last_time_sane = true; + m_is_last_time_sane.store(true, std::memory_order_relaxed); return; } - m_last_raw_dt = diff; + m_last_raw_dt.store(diff, std::memory_order_relaxed); m_raw_dts.Push(diff); } void PerformanceTracker::UpdateStats() { - DT diff{}; - while (m_raw_dts.Pop(diff)) - HandleRawDt(diff); - - // Update Std Dev - MathUtil::RunningVariance<double> variance; - for (auto dt : m_dt_queue) - variance.Push(DT_s(dt).count()); - m_dt_std = std::chrono::duration_cast<DT>(DT_s(variance.PopulationStandardDeviation())); -} + if (m_raw_dts.Empty()) + return; -void PerformanceTracker::HandleRawDt(DT diff) -{ - if (m_dt_queue.size() == MAX_DT_QUEUE_SIZE) - PopBack(); + const DT window{GetSampleWindow()}; + auto hz_avg = m_hz_avg.load(std::memory_order_relaxed); - PushFront(diff); + DT diff{}; + while (m_raw_dts.Pop(diff)) + { + if (m_dt_queue.size() == MAX_DT_QUEUE_SIZE) + PopBack(); - const DT window{GetSampleWindow()}; + PushFront(diff); - while (m_dt_total - m_dt_queue.back() >= window) - PopBack(); + while (DT(static_cast<long>(m_running_variance.Total())) - m_dt_queue.back() >= window) + PopBack(); - // Simple Moving Average Throughout the Window - const DT dt_avg = m_dt_total / m_dt_queue.size(); - const double hz = DT_s(1.0) / dt_avg; - m_dt_avg = dt_avg; + // Simple Moving Average Throughout the Window + const double hz = DT_s(1.0) / DT(static_cast<long>(m_running_variance.Mean())); - // Exponential Moving Average - const DT_s rc = SAMPLE_RC_RATIO * std::min(window, m_dt_total); - const double a = 1.0 - std::exp(-(DT_s(diff) / rc)); + // Exponential Moving Average + const DT_s rc = SAMPLE_RC_RATIO * DT(static_cast<long>(m_running_variance.Total())); + const double a = 1.0 - std::exp(-(DT_s(diff) / rc)); - // Sometimes euler averages can break when the average is inf/nan - const auto hz_avg = m_hz_avg.load(); - if (std::isfinite(hz_avg)) - m_hz_avg = std::lerp(hz_avg, hz, a); - else - m_hz_avg = hz; + // Sometimes euler averages can break when the average is inf/nan + if (std::isfinite(hz_avg)) + hz_avg = std::lerp(hz_avg, hz, a); + else + hz_avg = hz; - LogRenderTimeToFile(diff); + LogRenderTimeToFile(diff); + } + m_dt_avg.store(DT(static_cast<long>(m_running_variance.Mean())), std::memory_order_relaxed); + m_hz_avg.store(hz_avg, std::memory_order_relaxed); + m_dt_std.store(DT(static_cast<long>(m_running_variance.PopulationStandardDeviation())), + std::memory_order_relaxed); } DT PerformanceTracker::GetSampleWindow() const @@ -109,27 +105,27 @@ DT PerformanceTracker::GetSampleWindow() const double PerformanceTracker::GetHzAvg() const { - return m_hz_avg; + return m_hz_avg.load(std::memory_order_relaxed); } DT PerformanceTracker::GetDtAvg() const { - return m_dt_avg; + return m_dt_avg.load(std::memory_order_relaxed); } DT PerformanceTracker::GetDtStd() const { - return m_dt_std; + return m_dt_std.load(std::memory_order_relaxed); } DT PerformanceTracker::GetLastRawDt() const { - return m_last_raw_dt; + return m_last_raw_dt.load(std::memory_order_relaxed); } void PerformanceTracker::InvalidateLastTime() { - m_is_last_time_sane = false; + m_is_last_time_sane.store(false, std::memory_order_relaxed); } void PerformanceTracker::ImPlotPlotLines(const char* label) const @@ -175,12 +171,12 @@ void PerformanceTracker::ImPlotPlotLines(const char* label) const void PerformanceTracker::PushFront(DT value) { m_dt_queue.push_front(value); - m_dt_total += value; + m_running_variance.Push(value.count()); } void PerformanceTracker::PopBack() { - m_dt_total -= m_dt_queue.back(); + m_running_variance.Pop(m_dt_queue.back().count()); m_dt_queue.pop_back(); } diff --git a/Source/Core/VideoCommon/PerformanceTracker.h b/Source/Core/VideoCommon/PerformanceTracker.h index e210a8497c..508cfdd960 100644 --- a/Source/Core/VideoCommon/PerformanceTracker.h +++ b/Source/Core/VideoCommon/PerformanceTracker.h @@ -44,7 +44,6 @@ public: private: void LogRenderTimeToFile(DT val); - void HandleRawDt(DT value); void PushFront(DT value); void PopBack(); @@ -64,8 +63,12 @@ private: // Amount of time to sample dt's over (defaults to config) const std::optional<DT> m_sample_window_duration; - // Queue + Running Total used to calculate average dt - DT m_dt_total = DT::zero(); + // For calculating the average dt and standard deviation + // Using `double` because the alternative DT::rep (ns) risks causing an overflow, since + // RunningVariance stores the current std squared. + // (Even though it's basically impossible to achieve in practice, it'd need a + // more-than-a-minute long stutter followed by only almost-zero frametimes.) + MathUtil::RunningVariance<double> m_running_variance{}; std::deque<DT> m_dt_queue; // Average rate/time throughout the window |
