FlyByWireCpp/utils.hpp

453 lines
20 KiB
C++

#ifndef DEF_utils
#define DEF_utils
/// \file utils.hpp
/// \brief This file is a header-only "library" of useful tools that are often needed when developping in C++.
///
/// The file contains the following tools (non-exhaustive list) :
/// Macros :
/// - `PRINT_VAR`, `PRINT_VEC`, and `PRINT_STR` macros to print the name of variables as well as the variables themselves.
/// - `RAND_A_B`, and `RAND_0_1`, to generate double precision floating point random numbers uniformly spread over an interval (using stdlib's rand()).
/// - `MIN` and `MAX` macros to return the min or max of two values (note that both arguments are evaluated twice).
///
/// Template functions :
/// - `min` and `max` to return the min or max of two values (they are evaluated only once).
/// - `sureAbs` to return the absolute value of a numeric type (that is never going to cast a floating point number into a integer like ::abs often does).
/// - `clamp(x, a, b)` to constrain a value `x` to an interval [a, b].
/// - `operator<<(std::ostream& os, const std::vector<T> & objs)` to print the contents of an std::vector to an ostream.
/// - `operator<<(std::ostream& os, const std::list<T> & objs)` to print the contents of an std::list to an ostream.
///
/// Not really template functions :
/// - `rand_xorwow` and `rand_parkmiller` to generate uniformly distributed pseudo-random numbers. Very fast. Very nice.
///
/// Classes :
/// - `Chronometer` to measure execution times, with convenient macros that hide the inner workings (see documentation of `Chronometer` for more info).
/// - `Profiler` to measure execution times of individual lines, with convenient macros that hide the inner workings (see documentation of `Profiler` for more info).
///
/// Some tools can be deactivated by defining the following defines BEFORE including `utils.hpp` :
/// - UTILS_NO_RAND : Deactivates the `rand_xorwow` and `rand_parkmiller` functions. Removes the inclusion of <cstdint>.
/// - UTILS_NO_CHRONO : Deactivates the `Chronometer` class. Removes the inclusion of the following standard headers : <iostream> <chrono> <ratio> <ctime>.
/// - UTILS_NO_PROFILER : Deactivates the `Profiler` class. Removes the inclusion of the following standard headers : <iostream> <iomanip> <chrono> <ratio> <ctime> <cmath> <algorithm> <vector>.
/// - UTILS_NO_OSTREAM_OPERATOR : Deactivates the `operator<<` template function for BOTH `std::vector` and `std::list`. In order to deactivate only one of the two, use `UTILS_NO_OSTREAM_OPERATOR_LIST` or `UTILS_NO_OSTREAM_OPERATOR_VECTOR`.
/// - UTILS_NO_OSTREAM_OPERATOR_LIST : Deactivates the `operator<<` template function for `std::list` ONLY.
/// - UTILS_NO_OSTREAM_OPERATOR_VECTOR : Deactivates the `operator<<` template function for `std::vector` ONLY.
#include <ostream>
#include <utility>
#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << "\n"
#define PRINT_VEC(x); {std::cout << #x << "\t= "; \
for(unsigned int i_print_vec = 0 ; i_print_vec < (x).size() ; i_print_vec++) \
std::cout << (x)[i_print_vec] << "\t"; \
std::cout << "\n";}
#define PRINT_STR(x) std::cout << (x) << "\n"
#define PRINT_LINE() std::cout << __FILE__ << ':' << __LINE__ << "\n"
#define RAND_A_B(a, b) ((double(rand())/RAND_MAX)*((b)-(a)) + (a))
#define RAND_0_1() (double(rand())/RAND_MAX)
#define MIN(a, b) ((a) < (b)) ? (a) : (b)
#define MAX(a, b) ((a) > (b)) ? (a) : (b)
template<typename T, typename T2> auto min(const T & a, const T2 & b) -> decltype(a | b) { return (a < b) ? T(a) : T(b); }
template<typename T, typename T2> auto max(const T & a, const T2 & b) -> decltype(a | b) { return (a < b) ? T(b) : T(a); }
/// To be sure that floating point numbers won't be casted to integers in ::abs()...
template<typename T> T sureAbs(const T & x) { return (x < T(0)) ? -x : x; }
/// Clamps the input so that it remains within the interval [a, b]
template<typename T>
T clamp(const T & x, const T & a, const T & b)
{
if(x < a)
return a;
if(x > b)
return b;
return x;
}
#ifndef UTILS_NO_OSTREAM_OPERATOR
#ifndef UTILS_NO_OSTREAM_OPERATOR_VECTOR
#include <vector>
template<typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T> & objs)
{
for(auto const& obj : objs)
os << obj << ' ';
return os;
}
#endif // UTILS_NO_OSTREAM_OPERATOR_VECTOR
#ifndef UTILS_NO_OSTREAM_OPERATOR_LIST
#include <list>
template<typename T>
std::ostream& operator<<(std::ostream& os, const std::list<T> & objs)
{
for(auto const& obj : objs)
os << obj << ' ';
return os;
}
#endif // UTILS_NO_OSTREAM_OPERATOR_LIST
/*
/// An implementation of operator<< for ostream, (to print std::vector, std::list, etc).
template<typename T, template<class> class C>
std::ostream& operator<<(std::ostream& os, const C<T> & objs)
{
for (auto const& obj : objs)
os << obj << ' ';
return os;
}//*/
/*
/// Generic operator<< for ostream, (to print std::vector, etd::list, std::map, etc). Can generate conflicts with specialized operator<< overloads.
template<typename T, template<class,class...> class C, class... Args>
std::ostream& operator<<(std::ostream& os, const C<T,Args...>& objs)
{
for (auto const& obj : objs)
os << obj << ' ';
return os;
}//*/
#endif // UTILS_NO_OSTREAM_OPERATOR
// Define UTILS_NO_RAND to deactivate the pseudo random number generation (and the inclusion of cstdint).
#ifndef UTILS_NO_RAND
#include <cstdint>
#define RAND_MAX_XORWOW ((unsigned int)(4294967295))
#define RAND_MAX_XORWOW_F (4294967295.0f)
#define RAND_MAX_XORWOW_D (4294967295.0)
#define RAND_XORWOW_A_B(state, a, b) ((double(rand_xorwow(state))/RAND_MAX_XORWOW_D)*((b)-(a)) + (a))
#define RAND_XORWOW_0_1(state) (double(rand_xorwow(state))/RAND_MAX_XORWOW_D)
/// Pseudo-random number generator algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs".
///
/// The period is 2^160-2^32 ~= 10^48 numbers.
/// The first 4 elements of the state must be initialized to non-zero elements. The 5th element can also be zero.
/// Performs well but fails a few tests in BigCrush.
/// https://en.wikipedia.org/wiki/Xorshift#xorwow
template<int N=1>
uint32_t rand_xorwow(uint32_t state[5])
{
uint32_t s, t = state[3];
state[3] = state[2];
state[2] = state[1];
state[1] = s = state[0];
t ^= t >> 2;
t ^= t << 1;
state[0] = t ^= s ^ (s << 4);
return t + (state[4] += 362437);
}
/// VERY simple uniform random numbers generator (Park Miller).
///
/// The period is 2^31-1 = 2147483647 numbers.
/// http://www.cems.uwe.ac.uk/~irjohnso/coursenotes/ufeen8-15-m/p1192-parkmiller.pdf
/// http://www0.cs.ucl.ac.uk/staff/ucacbbl/ftp/papers/langdon_2009_CIGPU.pdf
template<int N=1>
int32_t rand_parkmiller(int32_t & seed) // 1 <= seed < m
{
const int32_t a = 16807; // ie 7**5
const int32_t m = 2147483647; // ie 2**31-1
seed = (int64_t(seed) * a) % m;
return seed;
}
#endif // UTILS_NO_RAND
// Define UTILS_NO_CHRONO to deactivate the chronometer.
#ifndef UTILS_NO_CHRONO
#include <iostream>
#include <chrono>
#include <ratio>
#include <ctime>
#define TIMER(str) Chronometer __chrono((str)); ///< Convenience macro to create a Chronometer object that will measure the execution time of its scope.
/// Convenience macro to create a Chronometer object that will measure the execution time of its scope, with a for-loop enveloping the code to time.
///
/// Usage : `TIMER_FOR("My timer", N, /* The code will be executed N times ! */)`
#define TIMER_FOR(str, N, code) {Chronometer __chrono((str)); for(size_t i_TIMER_FOR = 0 ; i_TIMER_FOR < N ; i_TIMER_FOR++) {code} }
/// \brief Chronometer class. Used to measure the execution time of its scope. Uses C++11 <chrono>.
///
/// The chronometer can be disabled by defining `UTILS_NO_CHRONO` before including the header. All the chronometer macros will be disabled safely and can be left in the code.
///
/// It is recommended to use the macros to use the Chronometer class.
///
/// Usage :
/// \code{.cpp}
/// {
/// TIMER("My timer");
/// /* some code to be profiled */
/// }// <--- the chronometer measures the time between the call to its constructor and the call to its destructor.
/// \endcode
///
/// Possible output :
///
/// My timer : 0.12317568 s (+/- 1e-09 s)
///
/// Alternate usage :
/// \code{.cpp}
/// {
/// Chronometer chronom("", false);
/// /* some code to time */
/// chronom.MeasureTimeElapsed();// prints how much time elapsed since the creation of the object
/// /* some code to time */
/// chronom.MeasureTimeElapsed();// prints how much time elapsed since the creation of the object
/// chronom.Reset();// resets the timer to 0
/// /* some code to time */
/// chronom.MeasureTimeElapsed();// prints how much time elapsed since the last reset
/// } // no message is displayed because displayAtDestruction_ is set to false
/// \endcode
///
/// A loop-macro for averaging time measurements is provided for convenience :
/// \code{.cpp}
/// TIMER_FOR("My timer", N, /* Code with varying execution time that will be executed N times. */)
/// \endcode
///
/// It allows easy averaging of a code that would have a highly varying execution time.
///
template<int T=1>
struct Chronometer
{
std::string name;
std::chrono::high_resolution_clock::time_point t0;
bool displayAtDestruction;
/// Creates the Chronometer object with the provided name and stores the time.
///
/// \param name_ : Name to display
/// \param displayAtDestruction_ : if true, a message is printed to the console during the destruction of the object.
Chronometer(const std::string & name_ = "", bool displayAtDestruction_ = true)
: name(name_),
t0(std::chrono::high_resolution_clock::now()),
displayAtDestruction(displayAtDestruction_)
{}
/// Measures the time elapsed between the creation of the object and its destruction, and prints the result in std::cout.
~Chronometer() { if(displayAtDestruction) { MeasureTimeElapsed(); } }
/// Resets the timer. Allows the measurement of the execution time of several sub-intervals in the current scope.
void Reset() { t0 = std::chrono::high_resolution_clock::now(); }
double GetTime() const
{
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> time_span = std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0);
return time_span.count();
}
double GetResolution() const
{
auto resolution_ratio = std::chrono::high_resolution_clock::period();
return double(resolution_ratio.num)/resolution_ratio.den;
}
/// Measures the time elapsed between the creation of the object and its destruction, and prints the result in std::cout. If displayname = "", the name stored in the object is used.
void MeasureTimeElapsed(const std::string & displayname = "") const
{
// measure time since object creation and print it in the console
const std::string *newname = &name;
if(displayname.size())
newname = &displayname;
std::cout.precision(16);
std::cout << newname->c_str() << "\t: " << GetTime() << " s (+/- " << GetResolution() << " s)" << std::endl;// use of c_str() so that there is no ambiguity with the generic operator<<
}
};
#else // UTILS_NO_CHRONO defined
#define TIMER(str)
#define TIMER_FOR(str, N, code) { for(size_t i_TIMER_FOR = 0 ; i_TIMER_FOR < N ; i_TIMER_FOR++) {code} }
#endif // UTILS_NO_CHRONO
// Define UTILS_NO_PROFILER to deactivate the profiler.
#ifndef UTILS_NO_PROFILER
#include <iostream>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <chrono>
#include <ctime>
/// Maximum number of lines to store. If the file you want to profile has more lines than this value, change the value of this define before including the header.
#ifndef UTILS_PROFILER_NMAX
#define UTILS_PROFILER_NMAX 1000
#endif
#define P_LINE_INIT() Profiler _line_profiler(__FILE__); ///< Initializes the profiler object.
#define P_LINE_BEGIN() _line_profiler.Reset(); ///< Resets the timer of the profiler object.
#define P_LINE() _line_profiler.ProfileLine(__LINE__); ///< Measures the time elapsed since the last timer reset.
#define P_LINE_DISPLAY() _line_profiler.DisplayData(); ///< Prints the profiler results to the console.
/// \brief Profiler class. This class profiles a code line by line. It is intrusive : you need to modify your code in order to profile it.
///
/// Note that the presence of the profiler prevents the compiler from optimizing parts of the code, resulting in a much slower execution.
/// The profiler also adds overhead. In the example below, the profiled code is about 40 times slower when the profiler is active.
///
/// The profiler stores the measured execution time of each line in a static array stored on the stack. The size of this array is `UTILS_PROFILER_NMAX`, with a default value of 1000.
/// If the file you want to profile has more lines than this value, change the value of this define before including the header.
///
/// In order to use the profiler, it is recommended to use the provided macros : `P_LINE_INIT(), P_LINE_BEGIN(), P_LINE(), and P_LINE_DISPLAY()`.
///
/// The profiler can be disabled by defining `UTILS_NO_PROFILER` before including the header. The profiler macros can be left in the code without impacting its performance.
///
/// Here is an example of use :
/// \code{.cpp}
/// P_LINE_INIT() // Initializes the profiler object
/// {TIMER("Some code"); P_LINE()// Uses a regular timer to time the whole execution.
/// constexpr int N = 1000000; P_LINE()// Every P_LINE() call measures the time elapsed since the last reset.
/// float *a1 = new float[N]; P_LINE()// Every P_LINE() call also automatically resets the timer after measuring the time.
/// float *a2 = new float[N]; P_LINE()
///
/// for (size_t i = 0; i < N; i++) { P_LINE()// Placing the P_LINE() like so allows for the measurement of the condition checking and increment.
/// a1[i] = i; P_LINE()
/// a2[i] = N-i; P_LINE()
/// }
///
/// for (size_t i = 0; i < N; i++) { P_LINE()
/// float v = std::sqrt(a1[i]*a1[i] + a2[i]*a2[i]); P_LINE()
/// a1[i] = v; P_LINE()
/// }
///
/// /* Code that does not matter for profiling */
///
/// P_LINE_BEGIN() // Resets the timer of the profiler.
/// delete[] a1; P_LINE()// Measures only the time of this line (instead of everything from the last P_LINE() call).
/// delete[] a2; P_LINE()
/// }
///
/// // display results of the profiler
/// {TIMER("P_LINE_DISPLAY()");
/// P_LINE_DISPLAY()
/// }
/// \endcode
///
/// Here is a possible output of the profiler :
/// \code
/// Some code : 0.199525752 s (+/- 1e-09 s)
/// Profiler results :
/// Total time of profiled lines = 102050953 ns (0.1020509530000000 s)
/// Profiler results sorted by ascending line number (times in ns) :
/// main.cpp:15 134 ( 0.0001 %)
/// main.cpp:16 30 ( 0.0000 %)
/// main.cpp:17 4129 ( 0.0040 %)
/// main.cpp:18 2063 ( 0.0020 %)
/// main.cpp:20 15958766 ( 15.6380 %)
/// main.cpp:21 17706218 ( 17.3504 %)
/// main.cpp:22 17500810 ( 17.1491 %)
/// main.cpp:25 15480408 ( 15.1693 %)
/// main.cpp:26 19006015 ( 18.6240 %)
/// main.cpp:27 16052417 ( 15.7298 %)
/// main.cpp:30 223831 ( 0.2193 %)
/// main.cpp:31 116132 ( 0.1138 %)
///
/// Profiler results sorted by desending line time (times in ns) :
/// main.cpp:26 19006015 ( 18.6240 %)
/// main.cpp:21 17706218 ( 17.3504 %)
/// main.cpp:22 17500810 ( 17.1491 %)
/// main.cpp:27 16052417 ( 15.7298 %)
/// main.cpp:20 15958766 ( 15.6380 %)
/// main.cpp:25 15480408 ( 15.1693 %)
/// main.cpp:30 223831 ( 0.2193 %)
/// main.cpp:31 116132 ( 0.1138 %)
/// main.cpp:17 4129 ( 0.0040 %)
/// main.cpp:18 2063 ( 0.0020 %)
/// main.cpp:15 134 ( 0.0001 %)
/// main.cpp:16 30 ( 0.0000 %)
/// P_LINE_DISPLAY() : 8.2959e-05 s (+/- 1e-09 s)
/// \endcode
template<int T=1>
class Profiler
{
protected:
std::string file_name; ///< File name to be displayed in the report.
int64_t line_times[UTILS_PROFILER_NMAX]; ///< Line times in nanoseconds. The values are stored like so : line_times[__LINE__] = time_for_the_line.
std::chrono::high_resolution_clock::time_point t0; ///< Time point storing the time when Reset() is called.
public:
Profiler(const std::string & file_name_ = "")
: file_name(file_name_)
{
// initialize the line times to 0
for (size_t i = 0; i < UTILS_PROFILER_NMAX; i++) {
line_times[i] = 0;
}
Reset();
}
/// Adds the time elapsed since the last reset to the corresponding line number. Usually called with __LINE__ as an argument.
void ProfileLine(int line_number)
{
line_times[line_number] += GetTime(); // increment the time of the line (when in loops)
Reset();
}
/// Displays detailed tables of the measured execution time of all the profiled lines.
void DisplayData()
{
// compute sum of times
int64_t sumOfTimes = 0;
for (size_t i = 0; i < UTILS_PROFILER_NMAX; i++) {
sumOfTimes += line_times[i];
}
// display the list sorted by line number
auto currentStreamPrecision = std::cout.precision();
std::cout << std::fixed;
std::cout << "Profiler results :\n";
std::cout << "Total time of profiled lines = " << sumOfTimes << " ns (" << double(sumOfTimes)/1e9 << " s)\n";
std::cout << "Profiler results sorted by ascending line number (times in ns) :\n";
for (size_t i = 0; i < UTILS_PROFILER_NMAX; i++)
if(line_times[i] != 0)
std::cout << file_name << ':' << i << std::setw(20) << line_times[i] << " (" << std::setw(8) << std::setprecision(4) << ((double(line_times[i])/sumOfTimes)*100.) << " %)" << std::endl;
// Display the list sorted by descending line time
{
// convert the static array to a vector of pairs...
std::vector<std::pair<int,int64_t>> line_times_vec;
for (size_t i = 0; i < UTILS_PROFILER_NMAX; i++)
if(line_times[i] != 0)
line_times_vec.push_back(std::make_pair(i, line_times[i]));
// sort the vector of pairs
std::sort(line_times_vec.begin(), line_times_vec.end(), [](const std::pair<int,int64_t> & a, const std::pair<int,int64_t> & b) {
return a.second > b.second;// Descending order using the second entry of the pair
});
std::cout << "\nProfiler results sorted by desending line time (times in ns) :\n";
for (auto it = line_times_vec.begin() ; it != line_times_vec.end() ; ++it) {
std::cout << file_name << ':' << it->first << std::setw(20) << it->second << " (" << std::setw(8) << std::setprecision(4) << ((double(it->second)/sumOfTimes)*100.) << " %)" << std::endl;
}
}
std::cout.precision(currentStreamPrecision);// restore previous stream precision
std::cout << std::defaultfloat;
}
/// Returns the time elapsed since the last reset of the timer. The time is given in nanoseconds.
int GetTime() const
{
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds time_span = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0);
return time_span.count();
}
/// Resets the timer of the profiler.
void Reset() { t0 = std::chrono::high_resolution_clock::now(); }
};
#else // UTILS_NO_PROFILER defined
#define P_LINE_INIT()
#define P_LINE_BEGIN()
#define P_LINE()
#define P_LINE_DISPLAY()
#endif // UTILS_NO_PROFILER
#endif