summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorMartino Fontana <tinozzo123@gmail.com>2026-09-15 18:20:05 +0200
committerMartino Fontana <tinozzo123@gmail.com>2026-10-31 22:52:44 +0100
commitc6e63fb3ab481ec2b5fd41a8ea8ab9d7597250e1 (patch)
treed890bda626c78873dad7c11fc539152d11340814 /Source
parent613db73aeb2c086a816592df2fb193d73130101b (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.h23
-rw-r--r--Source/Core/VideoCommon/PerformanceTracker.cpp92
-rw-r--r--Source/Core/VideoCommon/PerformanceTracker.h9
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