135 lines
3.4 KiB
C++
135 lines
3.4 KiB
C++
// Copyright 2015 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
|
|
#include "common/assert.h"
|
|
#include "common/thread.h"
|
|
|
|
namespace Common {
|
|
namespace Profiling {
|
|
|
|
// If this is defined to 0, it turns all Timers into no-ops.
|
|
#ifndef ENABLE_PROFILING
|
|
#define ENABLE_PROFILING 1
|
|
#endif
|
|
|
|
using Duration = std::chrono::nanoseconds;
|
|
using Clock = std::chrono::high_resolution_clock;
|
|
|
|
/**
|
|
* Represents a timing category that measured time can be accounted towards. Should be declared as a
|
|
* global variable and passed to Timers.
|
|
*/
|
|
class TimingCategory final {
|
|
public:
|
|
TimingCategory(const char* name, TimingCategory* parent = nullptr);
|
|
|
|
unsigned int GetCategoryId() const {
|
|
return category_id;
|
|
}
|
|
|
|
/// Adds some time to this category. Can safely be called from multiple threads at the same time.
|
|
void AddTime(Duration amount) {
|
|
std::atomic_fetch_add_explicit(
|
|
&accumulated_duration, amount.count(),
|
|
std::memory_order_relaxed);
|
|
}
|
|
|
|
/**
|
|
* Atomically retrieves the accumulated measured time for this category and resets the counter
|
|
* to zero. Can be safely called concurrently with AddTime.
|
|
*/
|
|
Duration GetAccumulatedTime() {
|
|
return Duration(std::atomic_exchange_explicit(
|
|
&accumulated_duration, (Duration::rep)0,
|
|
std::memory_order_relaxed));
|
|
}
|
|
|
|
private:
|
|
unsigned int category_id;
|
|
std::atomic<Duration::rep> accumulated_duration;
|
|
};
|
|
|
|
/**
|
|
* Measures time elapsed between a call to Start and a call to Stop and attributes it to the given
|
|
* TimingCategory. Start/Stop can be called multiple times on the same timer, but each call must be
|
|
* appropriately paired.
|
|
*
|
|
* When a Timer is started, it automatically pauses a previously running timer on the same thread,
|
|
* which is resumed when it is stopped. As such, no special action needs to be taken to avoid
|
|
* double-accounting of time on two categories.
|
|
*/
|
|
class Timer {
|
|
public:
|
|
Timer(TimingCategory& category) : category(category) {
|
|
}
|
|
|
|
void Start() {
|
|
#if ENABLE_PROFILING
|
|
ASSERT(!running);
|
|
previous_timer = current_timer;
|
|
current_timer = this;
|
|
if (previous_timer != nullptr)
|
|
previous_timer->StopTiming();
|
|
|
|
StartTiming();
|
|
#endif
|
|
}
|
|
|
|
void Stop() {
|
|
#if ENABLE_PROFILING
|
|
ASSERT(running);
|
|
StopTiming();
|
|
|
|
if (previous_timer != nullptr)
|
|
previous_timer->StartTiming();
|
|
current_timer = previous_timer;
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
#if ENABLE_PROFILING
|
|
void StartTiming() {
|
|
start = Clock::now();
|
|
running = true;
|
|
}
|
|
|
|
void StopTiming() {
|
|
auto duration = Clock::now() - start;
|
|
running = false;
|
|
category.AddTime(std::chrono::duration_cast<Duration>(duration));
|
|
}
|
|
|
|
Clock::time_point start;
|
|
bool running = false;
|
|
|
|
Timer* previous_timer;
|
|
static thread_local Timer* current_timer;
|
|
#endif
|
|
|
|
TimingCategory& category;
|
|
};
|
|
|
|
/**
|
|
* A Timer that automatically starts timing when created and stops at the end of the scope. Should
|
|
* be used in the majority of cases.
|
|
*/
|
|
class ScopeTimer : public Timer {
|
|
public:
|
|
ScopeTimer(TimingCategory& category) : Timer(category) {
|
|
Start();
|
|
}
|
|
|
|
~ScopeTimer() {
|
|
Stop();
|
|
}
|
|
};
|
|
|
|
} // namespace Profiling
|
|
} // namespace Common
|