Trying to write Singleton for the first time. This is a timer that works with one function to handle timer's both start and stop and also printing result.
When compiling, I'm getting linker errors like this one:
:-1: ошибка: CMakeFiles/some_algorithms.dir/timer_singleton.cpp.obj:timer_singleton.cpp:(.rdata$.refptr._ZN15timer_singleton7counterE[.refptr._ZN15timer_singleton7counterE]+0x0): undefined reference to `timer_singleton::counter'
What causes this error and how do I fix it?
Here is my source code:
timer_singleton.h
#ifndef TIMER_SINGLETON_H
#define TIMER_SINGLETON_H
#pragma once
#include <iostream>
#include <chrono>
class timer_singleton
{
public:
timer_singleton(timer_singleton & other) = delete;
void operator=(const timer_singleton& other) = delete;
static timer_singleton * getInstance();
static void hit_the_clock();
private:
timer_singleton();
static timer_singleton * instance;
static std::chrono::high_resolution_clock clock;
static std::chrono::high_resolution_clock::time_point start;
static std::chrono::high_resolution_clock::time_point stop;
static size_t counter;
};
#endif // TIMER_SINGLETON_H
timer_singleton.cpp
#include "timer_singleton.h"
timer_singleton::timer_singleton()
{
clock = std::chrono::high_resolution_clock();
start = clock.now();
stop = clock.now();
counter = 0;
}
timer_singleton * timer_singleton::getInstance()
{
if (instance == nullptr)
{
instance = new timer_singleton();
}
return instance;
}
void timer_singleton::hit_the_clock()
{
if (counter % 2 == 1)
{
// Clocks start ticking
start = clock.now();
++counter;
}
else
{
// Clocks stop ticking and print time measured time
stop = clock.now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << "Measured time = " << duration.count() << " microseconds" << std::endl;
++counter;
}
}
main.cpp
#include "timer_singleton.h"
// ...
timer_singleton * timer = timer_singleton::getInstance();
timer->hit_the_clock();
// some calculations
timer->hit_the_clock();
The problem: Most non-constant static members need to be defined outside of the class definition in order to get the one-and-only-one instance that will be shared by all class instances. Normally this means that in timer_singleton.cpp you would have to add
timer_singleton::counter = 0; // allocate and initialize
But...
A singleton is effectively already static so the only static member should be the function that gets the instance. This makes the whole problem go away.
New code with comments about other useful changes:
class timer_singleton
{
public:
timer_singleton(timer_singleton &other) = delete;
void operator=(const timer_singleton &other) = delete;
static timer_singleton* getInstance();
void hit_the_clock(); // shouldn't be static
private:
timer_singleton();
// None of these should have been static
std::chrono::high_resolution_clock clock; // This clock could jump around,
// including backward, in time.
// Safer with a steady_clock
std::chrono::high_resolution_clock::time_point start;
std::chrono::high_resolution_clock::time_point stop;
size_t counter;
};
timer_singleton::timer_singleton():
start(clock.now()),
stop(start), // guaranteed to be same as start
counter(0)
{ // assignments replaced with initializations in member initializer list
}
timer_singleton* timer_singleton::getInstance()
{ // now using Meyers singelton
static timer_singleton instance;
return &instance; // consider adjusting to return a reference.
// Often a bit cleaner thanks to the no null guarantee
}
void timer_singleton::hit_the_clock()
{
auto now = clock.now(); // if timing is critical, the first thing
// you do is get the current time.
//if (counter % 2 == 1) // remember counter starts at 0, so first hit
// would stop, not start, the timer.
if (counter % 2 == 0)
{
// Clocks start ticking
start = now;
}
else
{
// Clocks stop ticking and print time measured time
stop = now;
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
std::cout << "Measured time = " << duration.count() << " microseconds" << std::endl;
}
++counter; // don't repeat yourself
}
Related
I'm adding a profiler to an existing evaluator. The evaluator is implemented in c++ (std17 x86 msvc) and I need to store 3 int32, 1 uint16 and 1 uint8 to represent the context of the execution frame. As this is interpreted code, I cannot write any kernel level driver to take snapshots, so we have to add it to the eval loop. As with all profiling, we want to avoid slowing down the actual computation. So far for the motivation/context of this question.
At a high level, the behavior is as follows: before every instruction gets executed by the evaluator, it calls a small function ("hey, i'm here right now"), the sampling profiler is only interested in that frame position once every X ms (or μs, but that's most likely pushing it). So we need 2 threads (let's ignore the serialization for now) that want to share data. We have a very frequent writer (one single thread), and an infrequent reader (a different single thread). We like to minimize the performance penalty on the write size. Note, sometimes the writer becomes slow, so it might be stuck on a frame for a few seconds, we would like to be able to observe this.
So to help this question, I've written a small benchmark setup.
#include <memory>
#include <chrono>
#include <string>
#include <iostream>
#include <thread>
#include <immintrin.h>
#include <atomic>
#include <cstring>
using namespace std;
typedef struct frame {
int32_t a;
int32_t b;
uint16_t c;
uint8_t d;
} frame;
class ProfilerBase {
public:
virtual void EnterFrame(int32_t a, int32_t b, uint16_t c, uint8_t d) = 0;
virtual void Stop() = 0;
virtual ~ProfilerBase() {}
virtual string Name() = 0;
};
class NoOp : public ProfilerBase {
public:
void EnterFrame(int32_t a, int32_t b, uint16_t c, uint8_t d) override {}
void Stop() override {}
string Name() override { return "NoOp"; }
};
class JustStore : public ProfilerBase {
private:
frame _current = { 0 };
public:
string Name() override { return "OnlyStoreInMember"; }
void EnterFrame(int32_t a, int32_t b, uint16_t c, uint8_t d) override {
_current.a = a;
_current.b = b;
_current.c = c;
_current.d = d;
}
void Stop() override {
if ((_current.a + _current.b + _current.c + _current.d) == _current.a) {
cout << "Make sure optimizer keeps the record around";
}
}
};
class WithSampler : public ProfilerBase {
private:
unique_ptr<thread> _sampling;
atomic<bool> _keepSampling = true;
protected:
const chrono::milliseconds _sampleEvery;
virtual void _snap() = 0;
virtual string _subname() = 0;
public:
WithSampler(chrono::milliseconds sampleEvery): _sampleEvery(sampleEvery) {
_sampling = make_unique<thread>(&WithSampler::_sampler, this);
}
void Stop() override {
_keepSampling = false;
_sampling->join();
}
string Name() override {
return _subname() + to_string(_sampleEvery.count()) + "ms";
}
private:
void _sampler() {
auto nextTick = chrono::steady_clock::now();
while (_keepSampling)
{
const auto sleepTime = nextTick - chrono::steady_clock::now();
if (sleepTime > chrono::milliseconds(0))
{
this_thread::sleep_for(sleepTime);
}
_snap();
nextTick += _sampleEvery;
}
}
};
struct checkedFrame {
frame actual;
int32_t check;
};
// https://rigtorp.se/spinlock/
struct spinlock {
std::atomic<bool> lock_ = { 0 };
void lock() noexcept {
for (;;) {
// Optimistically assume the lock is free on the first try
if (!lock_.exchange(true, std::memory_order_acquire)) {
return;
}
// Wait for lock to be released without generating cache misses
while (lock_.load(std::memory_order_relaxed)) {
// Issue X86 PAUSE or ARM YIELD instruction to reduce contention between
// hyper-threads
_mm_pause();
}
}
}
void unlock() noexcept {
lock_.store(false, std::memory_order_release);
}
};
class Spinlock : public WithSampler {
private:
spinlock _loc;
checkedFrame _current;
public:
using WithSampler::WithSampler;
string _subname() override { return "Spinlock"; }
void EnterFrame(int32_t a, int32_t b, uint16_t c, uint8_t d) override {
_loc.lock();
_current.actual.a = a;
_current.actual.b = b;
_current.actual.c = c;
_current.actual.d = d;
_current.check = a + b + c + d;
_loc.unlock();
}
protected:
void _snap() override {
_loc.lock();
auto snap = _current;
_loc.unlock();
if ((snap.actual.a + snap.actual.b + snap.actual.c + snap.actual.d) != snap.check) {
cout << "Corrupted snap!!\n";
}
}
};
static constexpr int32_t LOOP_MAX = 1000 * 1000 * 1000;
int measure(unique_ptr<ProfilerBase> profiler) {
cout << "Running profiler: " << profiler->Name() << "\n ";
cout << "\tProgress: ";
auto start_time = std::chrono::steady_clock::now();
int r = 0;
for (int32_t x = 0; x < LOOP_MAX; x++)
{
profiler->EnterFrame(x, x + x, x & 0xFFFF, x & 0xFF);
r += x;
if (x % (LOOP_MAX / 1000) == 0)
{
this_thread::sleep_for(chrono::nanoseconds(10)); // simulat that sometimes we do other stuff not like storing
}
if (x % (LOOP_MAX / 10) == 0)
{
cout << static_cast<int>((static_cast<double>(x) / LOOP_MAX) * 10);
}
if (x % 1000 == 0) {
_mm_pause(); // give the other threads some time
}
if (x == (LOOP_MAX / 2)) {
// the first half of the loop we take as warmup
// so now we take the actual time
start_time = std::chrono::steady_clock::now();
}
}
cout << "\n";
const auto done_calc = std::chrono::steady_clock::now();
profiler->Stop();
const auto done_writing = std::chrono::steady_clock::now();
cout << "\tcalc: " << chrono::duration_cast<chrono::milliseconds>(done_calc - start_time).count() << "ms\n";
cout << "\tflush: " << chrono::duration_cast<chrono::milliseconds>(done_writing - done_calc).count() << "ms\n";
return r;
}
int main() {
measure(make_unique<NoOp>());
measure(make_unique<JustStore>());
measure(make_unique<Spinlock>(chrono::milliseconds(1)));
measure(make_unique<Spinlock>(chrono::milliseconds(10)));
return 0;
}
Compiling this with /O2 in x86 mode on my machine gives this output:
Running profiler: NoOp
Progress: 0123456789
calc: 1410ms
flush: 0ms
Running profiler: OnlyStoreInMember
Progress: 0123456789
calc: 1368ms
flush: 0ms
Running profiler: Spinlock1ms
Progress: 0123456789
calc: 3952ms
flush: 4ms
Running profiler: Spinlock10ms
Progress: 0123456789
calc: 3985ms
flush: 11ms
(while this was compiled with msvc in VS2022, I think g++ --std=c++17 -O2 -m32 -pthread -o testing small-test-case.cpp should come close enough).
Here we see that the Spinlock based sampler adds a ~2.5x overhead to the one without any. I've profiled it, and as expected, a lot of time is spend on taking a lock (where in most cases, there was no need for the lock).
I am trying to make a libary so that I can reuse all my class in future. Down here I have a class that can calculate delta time for fps and other stuffs if needed in future. I noticed that my first output of running always be a negative power up of -7 or more. Is it because I use float to do the calculation that it create this imprecision?
class Timer {
public:
Timer();
~Timer();
void Update();
float GetDeltaTime() const;
private:
std::chrono::time_point<std::chrono::high_resolution_clock> myCurrentTime;
std::chrono::time_point<std::chrono::high_resolution_clock> myLastTime;
};
Timer::Timer()
{
myCurrentTime = std::chrono::high_resolution_clock::now();
myLastTime = std::chrono::high_resolution_clock::now();
}
void Timer::Update()
{
myLastTime = myCurrentTime;
myCurrentTime = std::chrono::high_resolution_clock::now();
}
float Timer::GetDeltaTime() const
{
std::chrono::duration<float> deltaTime = std::chrono::duration_cast<std::chrono::duration<float> >(myCurrentTime - myLastTime);
return deltaTime.count();
}
int main()
{
Timer myTimer;
while (true) {
myTimer.Update();
std::cout << "Delta time: " << myTimer.GetDeltaTime() << std::endl; //Delta time: 2e-07 more or less it always become like this for first output
Sleep(1000);
}
}
Result
I tried to cast duration of time to float before the function run but it still produce same result
I have made a small header-only timer struct for testing the speed of functions. I made it purely out of curiosity because I am learning C++.
I have a function called "TimeTask" which has 2 overloads, The first one takes a std::function(void()) as a parameter and records how long the function takes to execute. The other one is a template function that takes a void function with any number of parameters and records its speed.
struct Timer
{
private:
std::chrono::steady_clock::time_point begin, end;
float Duration = 0.0f;
public:
const float& getDuration() const
{
return Duration;
}
void StartTimer()
{
begin = std::chrono::steady_clock::now();
}
void StopTimer()
{
end = std::chrono::steady_clock::now();
std::chrono::duration<float, std::milli> ChronoDuration = end - begin;
Duration = ChronoDuration.count();
}
template<class...Args>
void TimeTask(std::function<void(Args...)> task, Args&&...args)
{
StartTimer();
task(std::forward<Args>(args)...);
StopTimer();
}
void TimeTask(std::function<void()> task)
{
StartTimer();
task();
StopTimer();
}
};
Quite simple really. I tested it with the following code:
void task_parameters(double j) {
for (double i = 0; i < j * j * j * j * j; i++)
{
};
std::cout << "Done\n";
}
void task_void()
{
return;
}
int main()
{
Timer timer;
timer.TimeTask<double>(task_parameters,15.0);
std::cout << timer.getDuration() << std::endl;
timer.TimeTask(task_void);
std::cout << timer.getDuration() << std::endl;
std::cin.get();
return 0;
}
The program compiles and runs as expected but the first time I run the TimeTask function with "double" as my parameter type, it gives me an error:
E0304 no instance of overloaded function "Timer::TimeTask" matches the argument list
The program will still run, but I was wondering if I could get rid of this error? Thank you
The code appears to be correct and the behaves as expected, but I don't understand why that error is appearing.
Here is some code which increments several chronometers in parallel:
main.cpp
using namespace std;
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <math.h>
#include <cstdlib>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <thread>
#include <vector>
#include <future>
#include "mychrono.hpp"
int main()
{
std::vector<Chronometer*> car_crono;
Chronometer chrono, output_chrono;
std::vector<std::thread> threads;
std::vector<std::future<Chronometer&>> futures;
std::thread th;
//future<Chronometer> ft;
for(int i = 0; i < 2; i++)
{
car_crono.push_back(new Chronometer);
}
while (1) {
for(int i = 0; i<2; i++)
{
//
// //threads.push_back(std::thread(&Chronometer::start_chrono, car_crono[i], std::ref(chrono)));
// auto ft = std::async(std::launch::async, &Chronometer::start_chrono, car_crono[i], std::ref(chrono));
//
// std::cout << "Hello-world" << std::endl;
futures.emplace_back(std::async(std::launch::async, &Chronometer::start_chrono, car_crono[i], std::ref(chrono)));
}
std::cout << "hello-world" << std::endl;
//auto ft = std::async(std::launch::async, &Chronometer::start_chrono, car_crono[0], std::ref(chrono));
//std::cout << "Hello-world-2" << std::endl;
for(auto&& f: futures){
std::cout << f.get() << '\n';
}
}
car_crono.clear();
}
mychrono.cpp
#include "mychrono.hpp"
#include <time.h>
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <sstream>
#include <thread>
//int Chronometer::hour(0), min(0), sec(0);
Chronometer::Chronometer() : hour(0), min(0), sec(0)
{
}
Chronometer& Chronometer::start_chrono(Chronometer& chrono)
{
// if(chrono.hour == 0 && chrono.min == 0 && chrono.sec == 0)
// {
bool condition = true;
while(condition) {
sleep(1);
chrono.sec++;
if(chrono.sec > 59) {
chrono.min++;
chrono.sec = 0;
}
if(chrono.min > 59) {
chrono.hour++;
chrono.sec = 0;
chrono.min = 0;
}
// if(chrono.sec == 10)
// {
// condition = false;
// }
std::cout << "chrono: " << chrono << std::endl;
}
return chrono;
//}
}
Chronometer& Chronometer::finish_chrono(Chronometer& chrono)
{
chrono.hour = 0;
chrono.sec = 0;
chrono.min = 0;
return chrono;
}
std::ostream& operator<<(std::ostream& flux, Chronometer t)
{
flux << t.hour << ":" << t.min << ":" << t.sec;
return flux;
}
Chronometer& Chronometer::operator=(const Chronometer& other)
{
// Guard self assignment
//if (this == &other)
return *this;
}
Chronometer::~Chronometer(){}
mychrono.hpp
#include <time.h>
#include <iostream>
#include <sstream>
#ifndef mychrono_hpp
#define mychrono_hpp
class Chronometer
{
private:
int hour, min, sec;
//std::stringstream ss;
//Chronometer chrono;
public:
Chronometer();
Chronometer& start_chrono(Chronometer& chrono);
Chronometer& finish_chrono(Chronometer& chrono);
friend std::ostream& operator<<(std::ostream& flux, Chronometer t);
Chronometer& operator=(const Chronometer& other);
~Chronometer();
};
#endif
My program runs well my two chronometers in parallel each other but still dependant of my while loop. For example here I will print "hello-world" once but need to wait my threads stop to print a second "hello-world" message in my while loop.
My question is how to make my threads runs in parallel an be completely independant of others instructions in my while loop ?
Tzig had a similar idea as mine, that is using condition variables and such.
I've made a full working example including comments and hopefully written for readability.
#include <chrono>
#include <iostream>
#include <iomanip>
#include <mutex>
#include <future>
#include <condition_variable>
//-----------------------------------------------------------------------------------------------------
// state of the timer.
enum class State
{
idle,
starting,
running,
stopping,
stopped
};
//-----------------------------------------------------------------------------------------------------
// helper class for use of std::condition_variable, makes code more readable
// takes into account the pitfalls of condition variables :
// https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables
template<typename T>
class StateVariable
{
public:
StateVariable() = delete;
StateVariable(const StateVariable&) = delete;
StateVariable(StateVariable&&) = delete;
StateVariable& operator=(const StateVariable&) = delete;
explicit StateVariable(const T& value) :
m_value{ value }
{
}
void operator=(const T& value) noexcept
{
{
std::unique_lock<std::mutex> lock(m_value_mutex);
m_value = value;
}
m_value_changed.notify_all();
}
// atomic check and set
T set_if(const T& from_value, const T& to_value) noexcept
{
{
std::unique_lock<std::mutex> lock(m_value_mutex);
if (m_value != from_value) return from_value;
m_value = to_value;
}
m_value_changed.notify_all();
return to_value;
}
const bool try_wait_for(const T& value, const std::chrono::steady_clock::duration& duration) const noexcept
{
auto pred = [this, value] { return (m_value == value); };
std::unique_lock<std::mutex> lock(m_value_mutex);
if (pred()) return true;
return m_value_changed.wait_for(lock, duration, pred);
}
void wait_for(const T& value) const
{
try_wait_for(value, std::chrono::steady_clock::duration::max());
}
private:
// mutables so I could make the const promises on wait
// that they wont change the observable state (m_value)
// of this class.
mutable std::mutex m_value_mutex;
mutable std::condition_variable m_value_changed;
std::atomic<T> m_value;
};
//-----------------------------------------------------------------------------------------------------
// helper class for storing elapsed time, helps with readability later on
class ElapsedTime
{
public:
explicit ElapsedTime(const std::chrono::steady_clock::duration& duration) :
m_duration{ duration }
{
}
auto hours() const
{
return std::chrono::duration_cast<std::chrono::hours>(m_duration).count();
}
auto minutes() const
{
return (std::chrono::duration_cast<std::chrono::minutes>(m_duration).count() % 60);
}
auto seconds() const
{
return (std::chrono::duration_cast<std::chrono::seconds>(m_duration).count() % 60);
}
private:
std::chrono::steady_clock::duration m_duration;
};
//-----------------------------------------------------------------------------------------------------
// formatter for ElapsedTime
std::ostream& operator<<(std::ostream& os, const ElapsedTime& t)
{
os << std::setfill('0') << std::setw(2) << t.hours() << ':';
os << std::setfill('0') << std::setw(2) << t.minutes() << ':';
os << std::setfill('0') << std::setw(2) << t.seconds();
return os;
}
//-----------------------------------------------------------------------------------------------------
// ChronoMeter class
// note I use std::chrono classes
class ChronoMeter final
{
public:
ChronoMeter() :
m_state{ State::idle },
m_duration{ std::chrono::steady_clock::duration::min() }
{
};
ChronoMeter(const ChronoMeter&) = delete;
ChronoMeter(ChronoMeter&&) = delete;
ChronoMeter& operator=(const ChronoMeter&) = delete;
void Start()
{
m_start_time = std::chrono::steady_clock::now();
// exercise for the reader, also allow stopped Chronometers to be restarted.
// for now just this simple state model
if (m_state.set_if(State::idle, State::starting) != State::starting)
{
throw std::runtime_error("only an idle ChronoMeter can be started");
}
// it is okay to capture "this" because the destructor of the
// chronometer synchronizes with termination of this thread through the future
m_future = std::async(std::launch::async, [this]
{
// Set indication that the thread has really started.
// this is important because when std::async returns, this thread exists
// but may not have been scheduled yet.
m_state = State::running;
do
{
// assigning a value to m_duration isn't atomic so protect it.
// we might be reading the value on another thread which might
// result in reading an intermediate state.
std::scoped_lock<std::mutex> lock{ m_data_mtx };
m_duration = std::chrono::steady_clock::now() - m_start_time;
// using a statevariable to check for stopping means it can respond
// during the one second delay and stop immediately.
// this is an advantage over using sleep
} while (!m_state.try_wait_for(State::stopping, std::chrono::seconds(1)));
m_state = State::stopped;
});
// Wait for the thread to have really started
// this way we have a clear post condition for start
m_state.wait_for(State::running);
}
void Stop()
{
// only allow a running Chronometer to be stopped.
// in all other states Stop does nothing
if (m_state.set_if(State::running, State::stopping) == State::stopping)
{
// synchronization with stopped state, as set by other thread
m_state.wait_for(State::stopped);
// future get is not really needed for synchronization.
// but if thread threw an exception it's rethrown here
m_future.get();
}
}
~ChronoMeter()
{
// Automatically stop thread if this hasn't already happened.
Stop();
}
const ElapsedTime Elapsed() const
{
std::scoped_lock<std::mutex> lock{ m_data_mtx };
return ElapsedTime{ m_duration };
}
private:
std::future<void> m_future;
StateVariable<State> m_state;
mutable std::mutex m_data_mtx;
std::chrono::steady_clock::time_point m_start_time;
std::chrono::steady_clock::duration m_duration;
};
int main()
{
ChronoMeter meter1;
ChronoMeter meter2;
meter1.Start();
std::this_thread::sleep_for(std::chrono::seconds(5));
auto elapsed_1_1 = meter1.Elapsed();
std::cout << "Meter 1 elapsed time " << elapsed_1_1 << std::endl;
meter2.Start();
std::this_thread::sleep_for(std::chrono::seconds(4));
auto elapsed_1_2 = meter1.Elapsed();
auto elapsed_2 = meter2.Elapsed();
std::cout << "Meter 1 elapsed time " << elapsed_1_2 << std::endl;
std::cout << "Meter 2 elapsed time " << elapsed_2 << std::endl;
meter1.Stop();
// not stopping meter2 (and it's thread) explicitly, this is done safely by destructor if needed
return 0;
}
I usually solve this problem by making the multithreaded object handle everything that has to do with multithreading, here's how I solved it in your case (I ended up rewriting a lot of things so maybe the behavior isn't exactly the one you want, you can use my code as a starting point):
main.cpp:
#include <iostream>
#include <vector>
#include "mychrono.hpp"
int main()
{
std::vector<Chronometer*> car_crono;
for(int i = 0; i < 2; i++)
{
car_crono.push_back(new Chronometer);
}
while (1) {
// std::cout << "hello-world" << std::endl;
Chronometer::Time t = car_crono[0]->get_time();
if(t.sec >= 10)
car_crono[0]->reset_chrono();
std::cout << "Seconds of T0: " << t.sec << std::endl;
std::cout << "T1: " << car_crono[1]->to_string() << std::endl;
}
car_crono.clear();
}
mychrono.hpp:
#ifndef mychrono_hpp
#define mychrono_hpp
#include <iostream>
#include <thread>
#include <memory>
#include <condition_variable>
#include <mutex>
#include <atomic>
class Chronometer
{
public:
struct Time {
int hour;
int min;
int sec;
};
Chronometer();
void reset_chrono();
friend std::ostream& operator<<(std::ostream& flux, Chronometer& t);
Chronometer& operator=(const Chronometer& other);
std::string to_string();
Time get_time();
~Chronometer();
private:
Time currentTime;
std::mutex timeMutex;
std::condition_variable conditionVariable;
std::unique_ptr<std::thread> thread;
std::mutex CVMutex;
std::atomic<bool> exitNow;
void thread_function();
};
#endif
mychrono.cpp:
#include "mychrono.hpp"
Chronometer::Chronometer() : currentTime.hour(0), currentTime.min(0), currentTime.sec(0)
{
thread.reset(new std::thread(&Chronometer::thread_function, this));
}
void Chronometer::reset_chrono()
{
std::lock_guard<std::mutex> lock(timeMutex);
currentTime.hour = 0;
currentTime.sec = 0;
currentTime.min = 0;
}
std::ostream& operator<<(std::ostream& flux, Chronometer& t)
{
flux << t.to_string();
return flux;
}
Chronometer& Chronometer::operator=(const Chronometer& other)
{
// Guard self assignment
//if (this == &other)
return *this;
}
std::string Chronometer::to_string()
{
std::lock_guard<std::mutex> lock(timeMutex);
return std::to_string(currentTime.hour) + ":" + std::to_string(currentTime.min) + ":" + std::to_string(currentTime.sec);
}
Time Chronometer::get_time()
{
return currentTime;
}
Chronometer::~Chronometer()
{
exitNow = true;
{
std::unique_lock<std::mutex> lock(CVMutex);
lock.unlock();
conditionVariable.notify_all();
}
thread->join();
}
void Chronometer::thread_function()
{
std::unique_lock<std::mutex> waitLock(CVMutex);
while(!exitNow)
{
sec++;
if(currentTime.sec > 59) {
std::lock_guard<std::mutex> lock(timeMutex);
currentTime.min++;
currentTime.sec = 0;
}
if(currentTime.min > 59) {
std::lock_guard<std::mutex> lock(timeMutex);
currentTime.hour++;
currentTime.sec = 0;
currentTime.min = 0;
}
// std::cout << "chrono: " << *this << std::endl; //Not thread safe be careful
conditionVariable.wait_for(waitLock, std::chrono::seconds(1));
}
}
EDIT: About your latest comment: you don't need to reset a chrono in its destructor as the data will be destroyed anyway. If you want to reset the counter while it's running you want to call Chronometer::reset_chrono() from you main function.
For the second part of your comment, I added a get_time function to the code (I also added a mutex to avoid data races, I completly forgot when I wrote the original answer). When you want to get the current time of a chrono from the main function you just call get_time() and use the struct it returns to get the info you want.
I added a small example to show how to use both functions. As you can see, the main function doesn't even need to know what threads are !
I may be wrong but from the questions you ask I feel maybe you're not used to how multithreading works. It's a very difficult concept and one of the few I feel you can't learn only through experience, if that's the case you might want to learn about it from dedicated sites such as this one. I think I pieced together that you speak french, here's a really good article (that was never finished apparently) about the theory of it and another one in french, more about the specifics of C++. If you understand the core concepts and just have a hard time with my code, I plan on commenting it all but for now Pepijn Kramer did a great job explaining what they did in their response.
The Problem: I'm writing a game (as a programming exercise) from scratch. I'm trying to limit the number of game logic loops ("ticks") per second. I've set it to an arbitrary 100 ticks/second. But no matter what I do, it seems to run at ~130 ticks/second. Could it possibly be rounding errors adding up? Something else? Thanks in advance for any help you can give.
Note: my codebase is much larger than this, but for the purposes of this question, I've stripped it down as much as possible without breaking the rate limiter.
The Output:
counter 1 sleep_for(5ms)
counter 2 sleep_for(2ms)
[snip]
counter 132 sleep_for(3ms)
counter 133 sleep_for(3ms)
133 TPS last 1003ms
counter 134 sleep_for(3ms)
counter 135 sleep_for(3ms)
[snip]
counter 265 sleep_for(3ms)
counter 266 sleep_for(3ms)
133 TPS last 1004ms
counter 267 sleep_for(3ms)
counter 268 sleep_for(3ms)
[snip]
counter 399 sleep_for(3ms)
counter 400 sleep_for(3ms)
134 TPS last 1006ms
The Code:
(The two main functions to look at are ThreadRateLimiter::Tock() and TickRateCounter::Tock())
#include <chrono>
#include <exception>
#include <fstream>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
const int TICK_RATE = 100;
const chrono::milliseconds TIME_PER_TICK =
chrono::duration_cast<chrono::milliseconds>(chrono::seconds(1)) / TICK_RATE;
template <class T>
class TickTocker
{
public:
virtual void Tick() = 0;
virtual T Tock() = 0;
virtual T TickTock()
{
Tick();
return Tock();
}
};
Ticker:
class Ticker : public TickTocker<long>
{
public:
friend ostream& operator<<(ostream& stream, const Ticker& counter);
Ticker() :
Ticker(0)
{}
Ticker(long counter) :
mTicks(counter),
mTicksLast(mTicks)
{}
Ticker(const Ticker& counter) :
Ticker(counter.mTicks)
{}
bool operator==(const long i)
{
return mTicks == i;
}
void Tick() override
{
mTicks++;
}
long Tock() override
{
long diff = mTicks - mTicksLast;
mTicksLast = mTicks;
return diff;
}
// private:
long mTicks;
long mTicksLast;
};
ostream& operator<<(ostream& stream, const Ticker& counter)
{
return (stream << "counter " << counter.mTicks);
}
TickTracker:
class TickTracker : public TickTocker<chrono::milliseconds>
{
public:
TickTracker() :
mTime(chrono::steady_clock::now()),
mLastTime(mTime)
{}
void Tick() override
{
mTime = chrono::steady_clock::now();
}
chrono::milliseconds Tock() override
{
chrono::milliseconds diff = chrono::duration_cast<chrono::milliseconds>(mTime - mLastTime);
mLastTime = mTime;
return diff;
}
protected:
chrono::time_point<chrono::steady_clock> mTime;
chrono::time_point<chrono::steady_clock> mLastTime;
};
ThreadRateLimiter:
class ThreadRateLimiter : public TickTracker
{
public:
ThreadRateLimiter() : TickTracker(),
mMsFast(chrono::milliseconds(0))
{}
void Tick() override
{
mCounter.Tick();
TickTracker::Tick();
if (mCounter == 1)
{
TickTracker::Tock();
}
}
chrono::milliseconds Tock()
{
chrono::milliseconds diff = TickTracker::Tock();
chrono::milliseconds remaining = TIME_PER_TICK - diff;
/*
* If we always sleep the full remaining time, we'll alternate between sleeping for "minimum" and "maximum" sleep
* times. Sleeping the full remaining time only when we exceed the average makes for more stable sleep times.
*/
bool fullSleep = (mMsFast.count() > (TIME_PER_TICK.count() / 2));
mMsFast += remaining;
if (mMsFast.count() > 0)
{
chrono::milliseconds sleep = fullSleep ? mMsFast : (chrono::milliseconds(mMsFast.count() / 2));
cout << mCounter << " sleep_for(" << sleep.count() << "ms)" << endl;
this_thread::sleep_for(mMsFast);
mMsFast -= sleep;
}
mCounter.Tock();
return remaining;
}
private:
Ticker mCounter;
chrono::milliseconds mMsFast;
};
TickRateCounter:
class TickRateCounter : public TickTracker
{
public:
TickRateCounter(string rateLabel) : TickTracker(),
mRateLabel(rateLabel)
{}
void Tick() override
{
mCounter.Tick();
TickTracker::Tick();
}
chrono::milliseconds Tock() override
{
if (chrono::duration_cast<chrono::seconds>(mTime - mLastTime).count() >= 1)
{
chrono::milliseconds duration = TickTracker::Tock();
cout << (mCounter.Tock() / chrono::duration_cast<chrono::seconds>(duration).count()) << " " << mRateLabel
<< " last " << duration.count() << "ms" << endl;
return duration;
}
return chrono::milliseconds(0);
}
// private:
Ticker mCounter;
string mRateLabel;
};
Main:
int main()
{
ThreadRateLimiter mRateLimiter;
TickRateCounter mTpsCounter("TPS"); // TPS = Ticks per second. Tick = one game loop
while (mTpsCounter.mCounter.mTicks < 400)
{
mRateLimiter.TickTock();
mTpsCounter.TickTock();
}
return 0;
}
The proper way to have rate limiting is:
//pseudocode follows
const frame_duration = something;
last = now();
while(true)
{
process_your_frame_here()
do
{
t = now();
sleep(0); // or whatever fits your system
}
while(t < last + frame_duration);
last = last + frame_duration; // THIS IS KEY
// last = now; // would not produce the right framerate
}
Basically, you delay your frame, as you seem to do. But when bookkeeping the time spent, you only add the time you wanted (frame_duration). So, over multiple frame, it evens out.
To elaborate, if now() starts at 1000 (whatever unit) and you set frame_duration to 200, frame 1 will only run after 1200, frame 2 after 1400, ... frame 100 after 20100, giving exact frame rates over long periods of time.