453 lines
20 KiB
C++
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
|