summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorGravatar Yuri Kunde Schlesner2015-02-05 14:53:25 -0200
committerGravatar Yuri Kunde Schlesner2015-03-01 21:47:13 -0300
commitcd1fbfcf1b70e365d81480ec0f56db19ed02454f (patch)
treeb220b105d1b8016bb258047683bf2d03795c8881 /src/common
parentMerge pull request #616 from archshift/5551 (diff)
downloadyuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.tar.gz
yuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.tar.xz
yuzu-cd1fbfcf1b70e365d81480ec0f56db19ed02454f.zip
Add profiling infrastructure and widget
Diffstat (limited to 'src/common')
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/profiler.cpp159
-rw-r--r--src/common/profiler.h134
-rw-r--r--src/common/profiler_reporting.h108
-rw-r--r--src/common/synchronized_wrapper.h69
-rw-r--r--src/common/thread.h19
6 files changed, 493 insertions, 0 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index b05c35546..daa2d59de 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SRCS
14 mem_arena.cpp 14 mem_arena.cpp
15 memory_util.cpp 15 memory_util.cpp
16 misc.cpp 16 misc.cpp
17 profiler.cpp
17 scm_rev.cpp 18 scm_rev.cpp
18 string_util.cpp 19 string_util.cpp
19 symbols.cpp 20 symbols.cpp
@@ -48,11 +49,14 @@ set(HEADERS
48 mem_arena.h 49 mem_arena.h
49 memory_util.h 50 memory_util.h
50 platform.h 51 platform.h
52 profiler.h
53 profiler_reporting.h
51 scm_rev.h 54 scm_rev.h
52 scope_exit.h 55 scope_exit.h
53 string_util.h 56 string_util.h
54 swap.h 57 swap.h
55 symbols.h 58 symbols.h
59 synchronized_wrapper.h
56 thread.h 60 thread.h
57 thread_queue_list.h 61 thread_queue_list.h
58 thunk.h 62 thunk.h
diff --git a/src/common/profiler.cpp b/src/common/profiler.cpp
new file mode 100644
index 000000000..c37546af0
--- /dev/null
+++ b/src/common/profiler.cpp
@@ -0,0 +1,159 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/profiler.h"
6#include "common/profiler_reporting.h"
7#include "common/assert.h"
8
9namespace Common {
10namespace Profiling {
11
12#if ENABLE_PROFILING
13thread_local Timer* Timer::current_timer = nullptr;
14#endif
15
16TimingCategory::TimingCategory(const char* name, TimingCategory* parent)
17 : accumulated_duration(0) {
18
19 ProfilingManager& manager = GetProfilingManager();
20 category_id = manager.RegisterTimingCategory(this, name);
21 if (parent != nullptr)
22 manager.SetTimingCategoryParent(category_id, parent->category_id);
23}
24
25ProfilingManager::ProfilingManager()
26 : last_frame_end(Clock::now()), this_frame_start(Clock::now()) {
27}
28
29unsigned int ProfilingManager::RegisterTimingCategory(TimingCategory* category, const char* name) {
30 TimingCategoryInfo info;
31 info.category = category;
32 info.name = name;
33 info.parent = TimingCategoryInfo::NO_PARENT;
34
35 unsigned int id = (unsigned int)timing_categories.size();
36 timing_categories.push_back(std::move(info));
37
38 return id;
39}
40
41void ProfilingManager::SetTimingCategoryParent(unsigned int category, unsigned int parent) {
42 ASSERT(category < timing_categories.size());
43 ASSERT(parent < timing_categories.size());
44
45 timing_categories[category].parent = parent;
46}
47
48void ProfilingManager::BeginFrame() {
49 this_frame_start = Clock::now();
50}
51
52void ProfilingManager::FinishFrame() {
53 Clock::time_point now = Clock::now();
54
55 results.interframe_time = now - last_frame_end;
56 results.frame_time = now - this_frame_start;
57
58 results.time_per_category.resize(timing_categories.size());
59 for (size_t i = 0; i < timing_categories.size(); ++i) {
60 results.time_per_category[i] = timing_categories[i].category->GetAccumulatedTime();
61 }
62
63 last_frame_end = now;
64}
65
66TimingResultsAggregator::TimingResultsAggregator(size_t window_size)
67 : max_window_size(window_size), window_size(0) {
68 interframe_times.resize(window_size, Duration::zero());
69 frame_times.resize(window_size, Duration::zero());
70}
71
72void TimingResultsAggregator::Clear() {
73 window_size = cursor = 0;
74}
75
76void TimingResultsAggregator::SetNumberOfCategories(size_t n) {
77 size_t old_size = times_per_category.size();
78 if (n == old_size)
79 return;
80
81 times_per_category.resize(n);
82
83 for (size_t i = old_size; i < n; ++i) {
84 times_per_category[i].resize(max_window_size, Duration::zero());
85 }
86}
87
88void TimingResultsAggregator::AddFrame(const ProfilingFrameResult& frame_result) {
89 SetNumberOfCategories(frame_result.time_per_category.size());
90
91 interframe_times[cursor] = frame_result.interframe_time;
92 frame_times[cursor] = frame_result.frame_time;
93 for (size_t i = 0; i < frame_result.time_per_category.size(); ++i) {
94 times_per_category[i][cursor] = frame_result.time_per_category[i];
95 }
96
97 ++cursor;
98 if (cursor == max_window_size)
99 cursor = 0;
100 if (window_size < max_window_size)
101 ++window_size;
102}
103
104static AggregatedDuration AggregateField(const std::vector<Duration>& v, size_t len) {
105 AggregatedDuration result;
106 result.avg = Duration::zero();
107
108 result.min = result.max = (len == 0 ? Duration::zero() : v[0]);
109
110 for (size_t i = 1; i < len; ++i) {
111 Duration value = v[i];
112 result.avg += value;
113 result.min = std::min(result.min, value);
114 result.max = std::max(result.max, value);
115 }
116 if (len != 0)
117 result.avg /= len;
118
119 return result;
120}
121
122static float tof(Common::Profiling::Duration dur) {
123 using FloatMs = std::chrono::duration<float, std::chrono::milliseconds::period>;
124 return std::chrono::duration_cast<FloatMs>(dur).count();
125}
126
127AggregatedFrameResult TimingResultsAggregator::GetAggregatedResults() const {
128 AggregatedFrameResult result;
129
130 result.interframe_time = AggregateField(interframe_times, window_size);
131 result.frame_time = AggregateField(frame_times, window_size);
132
133 if (result.interframe_time.avg != Duration::zero()) {
134 result.fps = 1000.0f / tof(result.interframe_time.avg);
135 } else {
136 result.fps = 0.0f;
137 }
138
139 result.time_per_category.resize(times_per_category.size());
140 for (size_t i = 0; i < times_per_category.size(); ++i) {
141 result.time_per_category[i] = AggregateField(times_per_category[i], window_size);
142 }
143
144 return result;
145}
146
147ProfilingManager& GetProfilingManager() {
148 // Takes advantage of "magic" static initialization for race-free initialization.
149 static ProfilingManager manager;
150 return manager;
151}
152
153SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator() {
154 static SynchronizedWrapper<TimingResultsAggregator> aggregator(30);
155 return SynchronizedRef<TimingResultsAggregator>(aggregator);
156}
157
158} // namespace Profiling
159} // namespace Common
diff --git a/src/common/profiler.h b/src/common/profiler.h
new file mode 100644
index 000000000..53c4f6eaf
--- /dev/null
+++ b/src/common/profiler.h
@@ -0,0 +1,134 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <chrono>
9
10#include "common/assert.h"
11#include "common/thread.h"
12
13namespace Common {
14namespace Profiling {
15
16// If this is defined to 0, it turns all Timers into no-ops.
17#ifndef ENABLE_PROFILING
18#define ENABLE_PROFILING 1
19#endif
20
21using Duration = std::chrono::nanoseconds;
22using Clock = std::chrono::high_resolution_clock;
23
24/**
25 * Represents a timing category that measured time can be accounted towards. Should be declared as a
26 * global variable and passed to Timers.
27 */
28class TimingCategory final {
29public:
30 TimingCategory(const char* name, TimingCategory* parent = nullptr);
31
32 unsigned int GetCategoryId() const {
33 return category_id;
34 }
35
36 /// Adds some time to this category. Can safely be called from multiple threads at the same time.
37 void AddTime(Duration amount) {
38 std::atomic_fetch_add_explicit(
39 &accumulated_duration, amount.count(),
40 std::memory_order_relaxed);
41 }
42
43 /**
44 * Atomically retrieves the accumulated measured time for this category and resets the counter
45 * to zero. Can be safely called concurrently with AddTime.
46 */
47 Duration GetAccumulatedTime() {
48 return Duration(std::atomic_exchange_explicit(
49 &accumulated_duration, (Duration::rep)0,
50 std::memory_order_relaxed));
51 }
52
53private:
54 unsigned int category_id;
55 std::atomic<Duration::rep> accumulated_duration;
56};
57
58/**
59 * Measures time elapsed between a call to Start and a call to Stop and attributes it to the given
60 * TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be
61 * appropriately paired.
62 *
63 * When a Timer is started, it automatically pauses a previously running timer on the same thread,
64 * which is resumed when it is stopped. As such, no special action needs to be taken to avoid
65 * double-accounting of time on two categories.
66 */
67class Timer {
68public:
69 Timer(TimingCategory& category) : category(category) {
70 }
71
72 void Start() {
73#if ENABLE_PROFILING
74 ASSERT(!running);
75 previous_timer = current_timer;
76 current_timer = this;
77 if (previous_timer != nullptr)
78 previous_timer->StopTiming();
79
80 StartTiming();
81#endif
82 }
83
84 void Stop() {
85#if ENABLE_PROFILING
86 ASSERT(running);
87 StopTiming();
88
89 if (previous_timer != nullptr)
90 previous_timer->StartTiming();
91 current_timer = previous_timer;
92#endif
93 }
94
95private:
96#if ENABLE_PROFILING
97 void StartTiming() {
98 start = Clock::now();
99 running = true;
100 }
101
102 void StopTiming() {
103 auto duration = Clock::now() - start;
104 running = false;
105 category.AddTime(std::chrono::duration_cast<Duration>(duration));
106 }
107
108 Clock::time_point start;
109 bool running = false;
110
111 Timer* previous_timer;
112 static thread_local Timer* current_timer;
113#endif
114
115 TimingCategory& category;
116};
117
118/**
119 * A Timer that automatically starts timing when created and stops at the end of the scope. Should
120 * be used in the majority of cases.
121 */
122class ScopeTimer : public Timer {
123public:
124 ScopeTimer(TimingCategory& category) : Timer(category) {
125 Start();
126 }
127
128 ~ScopeTimer() {
129 Stop();
130 }
131};
132
133} // namespace Profiling
134} // namespace Common
diff --git a/src/common/profiler_reporting.h b/src/common/profiler_reporting.h
new file mode 100644
index 000000000..3abb73315
--- /dev/null
+++ b/src/common/profiler_reporting.h
@@ -0,0 +1,108 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <chrono>
9#include <mutex>
10#include <utility>
11#include <vector>
12
13#include "common/profiler.h"
14#include "common/synchronized_wrapper.h"
15
16namespace Common {
17namespace Profiling {
18
19struct TimingCategoryInfo {
20 static const unsigned int NO_PARENT = -1;
21
22 TimingCategory* category;
23 const char* name;
24 unsigned int parent;
25};
26
27struct ProfilingFrameResult {
28 /// Time since the last delivered frame
29 Duration interframe_time;
30
31 /// Time spent processing a frame, excluding VSync
32 Duration frame_time;
33
34 /// Total amount of time spent inside each category in this frame. Indexed by the category id
35 std::vector<Duration> time_per_category;
36};
37
38class ProfilingManager final {
39public:
40 ProfilingManager();
41
42 unsigned int RegisterTimingCategory(TimingCategory* category, const char* name);
43 void SetTimingCategoryParent(unsigned int category, unsigned int parent);
44
45 const std::vector<TimingCategoryInfo>& GetTimingCategoriesInfo() const {
46 return timing_categories;
47 }
48
49 /// This should be called after swapping screen buffers.
50 void BeginFrame();
51 /// This should be called before swapping screen buffers.
52 void FinishFrame();
53
54 /// Get the timing results from the previous frame. This is updated when you call FinishFrame().
55 const ProfilingFrameResult& GetPreviousFrameResults() const {
56 return results;
57 }
58
59private:
60 std::vector<TimingCategoryInfo> timing_categories;
61 Clock::time_point last_frame_end;
62 Clock::time_point this_frame_start;
63
64 ProfilingFrameResult results;
65};
66
67struct AggregatedDuration {
68 Duration avg, min, max;
69};
70
71struct AggregatedFrameResult {
72 /// Time since the last delivered frame
73 AggregatedDuration interframe_time;
74
75 /// Time spent processing a frame, excluding VSync
76 AggregatedDuration frame_time;
77
78 float fps;
79
80 /// Total amount of time spent inside each category in this frame. Indexed by the category id
81 std::vector<AggregatedDuration> time_per_category;
82};
83
84class TimingResultsAggregator final {
85public:
86 TimingResultsAggregator(size_t window_size);
87
88 void Clear();
89 void SetNumberOfCategories(size_t n);
90
91 void AddFrame(const ProfilingFrameResult& frame_result);
92
93 AggregatedFrameResult GetAggregatedResults() const;
94
95 size_t max_window_size;
96 size_t window_size;
97 size_t cursor;
98
99 std::vector<Duration> interframe_times;
100 std::vector<Duration> frame_times;
101 std::vector<std::vector<Duration>> times_per_category;
102};
103
104ProfilingManager& GetProfilingManager();
105SynchronizedRef<TimingResultsAggregator> GetTimingResultsAggregator();
106
107} // namespace Profiling
108} // namespace Common
diff --git a/src/common/synchronized_wrapper.h b/src/common/synchronized_wrapper.h
new file mode 100644
index 000000000..946252b8c
--- /dev/null
+++ b/src/common/synchronized_wrapper.h
@@ -0,0 +1,69 @@
1// Copyright 2015 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <mutex>
8
9namespace Common {
10
11/**
12 * Wraps an object, only allowing access to it via a locking reference wrapper. Good to ensure no
13 * one forgets to lock a mutex before acessing an object. To access the wrapped object construct a
14 * SyncronizedRef on this wrapper. Inspired by Rust's Mutex type (http://doc.rust-lang.org/std/sync/struct.Mutex.html).
15 */
16template <typename T>
17class SynchronizedWrapper {
18public:
19 template <typename... Args>
20 SynchronizedWrapper(Args&&... args) :
21 data(std::forward<Args>(args)...) {
22 }
23
24private:
25 template <typename U>
26 friend class SynchronizedRef;
27
28 std::mutex mutex;
29 T data;
30};
31
32/**
33 * Synchronized reference, that keeps a SynchronizedWrapper's mutex locked during its lifetime. This
34 * greatly reduces the chance that someone will access the wrapped resource without locking the
35 * mutex.
36 */
37template <typename T>
38class SynchronizedRef {
39public:
40 SynchronizedRef(SynchronizedWrapper<T>& wrapper) : wrapper(&wrapper) {
41 wrapper.mutex.lock();
42 }
43
44 SynchronizedRef(SynchronizedRef&) = delete;
45 SynchronizedRef(SynchronizedRef&& o) : wrapper(o.wrapper) {
46 o.wrapper = nullptr;
47 }
48
49 ~SynchronizedRef() {
50 if (wrapper)
51 wrapper->mutex.unlock();
52 }
53
54 SynchronizedRef& operator=(SynchronizedRef&) = delete;
55 SynchronizedRef& operator=(SynchronizedRef&& o) {
56 std::swap(wrapper, o.wrapper);
57 }
58
59 T& operator*() { return wrapper->data; }
60 const T& operator*() const { return wrapper->data; }
61
62 T* operator->() { return &wrapper->data; }
63 const T* operator->() const { return &wrapper->data; }
64
65private:
66 SynchronizedWrapper<T>* wrapper;
67};
68
69} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index eaf1ba00c..a45728e1e 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -24,6 +24,25 @@
24#include <unistd.h> 24#include <unistd.h>
25#endif 25#endif
26 26
27// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very
28// recently. Fortunately, thread local variables have been well supported for compilers for a while,
29// but with semantics supporting only POD types, so we can use a few defines to get some amount of
30// backwards compat support.
31// WARNING: This only works correctly with POD types.
32#if defined(__clang__)
33# if !__has_feature(cxx_thread_local)
34# define thread_local __thread
35# endif
36#elif defined(__GNUC__)
37# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
38# define thread_local __thread
39# endif
40#elif defined(_MSC_VER)
41# if _MSC_VER < 1900
42# define thread_local __declspec(thread)
43# endif
44#endif
45
27namespace Common 46namespace Common
28{ 47{
29 48