PerformanceTracker.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // Copyright 2022 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "VideoCommon/PerformanceTracker.h"
  4. #include <algorithm>
  5. #include <cmath>
  6. #include <iomanip>
  7. #include <mutex>
  8. #include <implot.h>
  9. #include "Common/CommonTypes.h"
  10. #include "Common/FileUtil.h"
  11. #include "Common/Timer.h"
  12. #include "Core/Core.h"
  13. #include "VideoCommon/VideoConfig.h"
  14. static constexpr double SAMPLE_RC_RATIO = 0.25;
  15. PerformanceTracker::PerformanceTracker(const std::optional<std::string> log_name,
  16. const std::optional<s64> sample_window_us)
  17. : m_on_state_changed_handle{Core::AddOnStateChangedCallback([this](Core::State state) {
  18. if (state == Core::State::Paused)
  19. SetPaused(true);
  20. else if (state == Core::State::Running)
  21. SetPaused(false);
  22. })},
  23. m_log_name{log_name}, m_sample_window_us{sample_window_us}
  24. {
  25. Reset();
  26. }
  27. PerformanceTracker::~PerformanceTracker()
  28. {
  29. Core::RemoveOnStateChangedCallback(&m_on_state_changed_handle);
  30. }
  31. void PerformanceTracker::Reset()
  32. {
  33. std::unique_lock lock{m_mutex};
  34. QueueClear();
  35. m_last_time = Clock::now();
  36. m_hz_avg = 0.0;
  37. m_dt_avg = DT::zero();
  38. m_dt_std = std::nullopt;
  39. }
  40. void PerformanceTracker::Count()
  41. {
  42. std::unique_lock lock{m_mutex};
  43. if (m_paused)
  44. return;
  45. const DT window{GetSampleWindow()};
  46. const TimePoint time{Clock::now()};
  47. const DT diff{time - m_last_time};
  48. m_last_time = time;
  49. QueuePush(diff);
  50. m_dt_total += diff;
  51. if (m_dt_queue_begin == m_dt_queue_end)
  52. m_dt_total -= QueuePop();
  53. while (window <= m_dt_total - QueueTop())
  54. m_dt_total -= QueuePop();
  55. // Simple Moving Average Throughout the Window
  56. m_dt_avg = m_dt_total / QueueSize();
  57. const double hz = DT_s(1.0) / m_dt_avg;
  58. // Exponential Moving Average
  59. const DT_s rc = SAMPLE_RC_RATIO * std::min(window, m_dt_total);
  60. const double a = 1.0 - std::exp(-(DT_s(diff) / rc));
  61. // Sometimes euler averages can break when the average is inf/nan
  62. if (std::isfinite(m_hz_avg))
  63. m_hz_avg += a * (hz - m_hz_avg);
  64. else
  65. m_hz_avg = hz;
  66. m_dt_std = std::nullopt;
  67. LogRenderTimeToFile(diff);
  68. }
  69. DT PerformanceTracker::GetSampleWindow() const
  70. {
  71. // This reads a constant value and thus does not need a mutex
  72. return std::chrono::duration_cast<DT>(
  73. DT_us(m_sample_window_us.value_or(std::max(1, g_ActiveConfig.iPerfSampleUSec))));
  74. }
  75. double PerformanceTracker::GetHzAvg() const
  76. {
  77. std::shared_lock lock{m_mutex};
  78. return m_hz_avg;
  79. }
  80. DT PerformanceTracker::GetDtAvg() const
  81. {
  82. std::shared_lock lock{m_mutex};
  83. return m_dt_avg;
  84. }
  85. DT PerformanceTracker::GetDtStd() const
  86. {
  87. std::unique_lock lock{m_mutex};
  88. if (m_dt_std)
  89. return *m_dt_std;
  90. if (QueueEmpty())
  91. return *(m_dt_std = DT::zero());
  92. double total = 0.0;
  93. for (std::size_t i = m_dt_queue_begin; i != m_dt_queue_end; i = IncrementIndex(i))
  94. {
  95. double diff = DT_s(m_dt_queue[i] - m_dt_avg).count();
  96. total += diff * diff;
  97. }
  98. // This is a weighted standard deviation
  99. return *(m_dt_std = std::chrono::duration_cast<DT>(DT_s(std::sqrt(total / QueueSize()))));
  100. }
  101. DT PerformanceTracker::GetLastRawDt() const
  102. {
  103. std::shared_lock lock{m_mutex};
  104. if (QueueEmpty())
  105. return DT::zero();
  106. return QueueBottom();
  107. }
  108. void PerformanceTracker::ImPlotPlotLines(const char* label) const
  109. {
  110. static std::array<float, MAX_DT_QUEUE_SIZE + 2> x, y;
  111. std::shared_lock lock{m_mutex};
  112. if (QueueEmpty())
  113. return;
  114. // Decides if there are too many points to plot using rectangles
  115. const bool quality = QueueSize() < MAX_QUALITY_GRAPH_SIZE;
  116. const DT update_time = Clock::now() - m_last_time;
  117. const float predicted_frame_time = DT_ms(std::max(update_time, QueueBottom())).count();
  118. std::size_t points = 0;
  119. if (quality)
  120. {
  121. x[points] = 0.f;
  122. y[points] = predicted_frame_time;
  123. ++points;
  124. }
  125. x[points] = DT_ms(update_time).count();
  126. y[points] = predicted_frame_time;
  127. ++points;
  128. const std::size_t begin = DecrementIndex(m_dt_queue_end);
  129. const std::size_t end = DecrementIndex(m_dt_queue_begin);
  130. for (std::size_t i = begin; i != end; i = DecrementIndex(i))
  131. {
  132. const float frame_time_ms = DT_ms(m_dt_queue[i]).count();
  133. if (quality)
  134. {
  135. x[points] = x[points - 1];
  136. y[points] = frame_time_ms;
  137. ++points;
  138. }
  139. x[points] = x[points - 1] + frame_time_ms;
  140. y[points] = frame_time_ms;
  141. ++points;
  142. }
  143. ImPlot::PlotLine(label, x.data(), y.data(), static_cast<int>(points));
  144. }
  145. void PerformanceTracker::QueueClear()
  146. {
  147. m_dt_total = DT::zero();
  148. m_dt_queue_begin = 0;
  149. m_dt_queue_end = 0;
  150. }
  151. void PerformanceTracker::QueuePush(DT dt)
  152. {
  153. m_dt_queue[m_dt_queue_end] = dt;
  154. m_dt_queue_end = IncrementIndex(m_dt_queue_end);
  155. }
  156. const DT& PerformanceTracker::QueuePop()
  157. {
  158. const std::size_t top = m_dt_queue_begin;
  159. m_dt_queue_begin = IncrementIndex(m_dt_queue_begin);
  160. return m_dt_queue[top];
  161. }
  162. const DT& PerformanceTracker::QueueTop() const
  163. {
  164. return m_dt_queue[m_dt_queue_begin];
  165. }
  166. const DT& PerformanceTracker::QueueBottom() const
  167. {
  168. return m_dt_queue[DecrementIndex(m_dt_queue_end)];
  169. }
  170. std::size_t PerformanceTracker::QueueSize() const
  171. {
  172. return GetDifference(m_dt_queue_begin, m_dt_queue_end);
  173. }
  174. bool PerformanceTracker::QueueEmpty() const
  175. {
  176. return m_dt_queue_begin == m_dt_queue_end;
  177. }
  178. void PerformanceTracker::LogRenderTimeToFile(DT val)
  179. {
  180. if (!m_log_name || !g_ActiveConfig.bLogRenderTimeToFile)
  181. return;
  182. if (!m_bench_file.is_open())
  183. {
  184. File::OpenFStream(m_bench_file, File::GetUserPath(D_LOGS_IDX) + *m_log_name,
  185. std::ios_base::out);
  186. }
  187. m_bench_file << std::fixed << std::setprecision(8) << DT_ms(val).count() << std::endl;
  188. }
  189. void PerformanceTracker::SetPaused(bool paused)
  190. {
  191. std::unique_lock lock{m_mutex};
  192. m_paused = paused;
  193. if (m_paused)
  194. {
  195. m_last_time = TimePoint::max();
  196. }
  197. else
  198. {
  199. m_last_time = Clock::now();
  200. }
  201. }