This question already has answers here:
Measuring execution time of a function in C++
(14 answers)
Closed 2 years ago.
Explication
Hello,
I want to create a function that can execute any type of function, indicating at the end of its execution the time it took.
The called function can have a return value or not and 0 or more parameters of any type.
The calling function must print something like this:
Running "myFunction" .....
Done ! (5210ms)
Basically I want to create a function that calls any type of function passed as a parameter by adding code before and after the call.
What I have done
For now I do it like this.
The calling function:
template <typename T>
T callFunctionPrintTime(std::string fnName, std::function<T()> fn) {
std::cout << ">> Running " << fnName << " ... " << std::endl;
auto t1 = std::chrono::high_resolution_clock::now();
//Call to the target function
T retVal = fn();
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
std::cout << "Done ! (" << duration << " ms)" << std::endl;
return retVal;
}
The main
int main()
{
//Store the function to call
std::function<unsigned long()> fn = []() {
return myFunction(15, 10000);
};
//Use of the function we are interested in
auto i = callFunctionPrintTime("myFunction", fn);
//The return value of myFunction can be used in the rest of the program.
std::cout << "i: " << i << std::endl;
}
myFunction
This function doesn't matter, it can be anything.
Here we execute a while loop for a given maximum time or maximum number of loop and retrieve the number of loop performed.
unsigned long myFunction(long maxMs, unsigned long maxI) {
unsigned long i = 0;
auto tStart = std::chrono::high_resolution_clock::now();
while (maxMs > (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - tStart).count()) &&
maxI > i) {
i++;
}
return i;
}
Question
What is the best way for you to do this? I am not satisfied with my code.
I'm not sure I'm using the right way to pass a function of any kind by parameter.
Moreover by using a lambda expression to store my function I can't retrieve the name of the called function. So I have to pass its name by parameter.
I'm pretty sure there's no single answer to what's best - but this is a small improvement i.m.o. since it's a bit more generic.
#include <chrono>
#include <iostream>
#include <string>
#include <type_traits>
// enable it for invocables with any type of arguments
template <class Func, class... Args,
std::enable_if_t<std::is_invocable_v<Func, Args...>, int> = 0>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
std::cout << ">> Running " << fnName << " ... " << std::endl;
auto t1 = std::chrono::high_resolution_clock::now();
//Call to the target function by forwarding the arguments to it
decltype(auto) retVal = fn(std::forward<Args>(args)...);
auto t2 = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
std::cout << "Done ! (" << duration << " ms)" << std::endl;
return retVal;
}
Alternatively, if you don't plan on making overloads for non-invocables (which seems pretty obvious that you wont when I think about it) you can use static_assert instead of SFINAE:
template <class Func, class... Args>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
static_assert(std::is_invocable_v<Func, Args...>, "must be invocable");
//...
Test usage:
int& a_func(int i) {
static int rv = 0;
rv += i;
return rv;
}
int main() {
int& ref = callFunctionPrintTime("a_func 1", a_func, 10);
std::cout << ref << '\n'; // prints 10
ref += 20;
callFunctionPrintTime("a_func 2", a_func, 100);
std::cout << ref << '\n'; // prints 130 (10 + 20 + 100)
}
Or some of the alternatives for calling myFunction:
std::function<unsigned long()> fn = []() { return myFunction(15, 100000); };
std::cout << callFunctionPrintTime("myFunction", fn);
std::cout << callFunctionPrintTime("myFunction",
[]() { return myFunction(15, 100000); });
std::cout << callFunctionPrintTime("myFunction", myFunction, 15, 100000);
Some useful links:
decltype(auto), std::enable_if_t, std::is_invocable_v, SFINAE
Main idea is correct. there are some details which might be improved:
template <typename Func, typename ... Ts>
decltype(auto) callFunctionPrintTime(std::string_view fnName, Func&& f, Ts&&... args) {
static_assert(std::is_invocable_v<Func&&, Ts&&...>); // Possibly SFINAE instead.
std::cout << ">> Running " << fnName << " ... " << std::endl;
struct Finally {
std::chrono::time_point<std::chrono::high_resolution_clock> t1 =
std::chrono::high_resolution_clock::now();
~Finally() {
auto t2 = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
std::cout << "Done ! (" << duration << " ms)" << std::endl;
}
} finally;
return std::invoke(std::forward<Func>(f), std::forward<Ts>(args)...);
}
Now:
handles void return type (without specialization required).
Log also in case of exception (You can go further with std::uncaught_exceptions or try/catch block to dissociate exception from normal path).
handle any invocable with its parameters.
For automatic name, we have to rely on MACRO:
#define CallFunctionPrintTime(F, ...) callFunctionPrintTime(#F, F __VA_OPT__(,) __VA_ARGS__)
Demo
Related
I have a quick & dirty macro which I used to quickly test my program:
#define ASSERT_EQUAL(expected, actualFunc, desc)
expected should be the first argument, and is a literal; actualFunc should be the second argument, and is a function call with parameters, an example call looks like:
ASSERT_EQUAL(true, test1(42), "cache-friendly");
In this macro, I will also timing the second argument(the function call), in order to make sure it works as expected, I need to make sure the parameters are passed in right order, or else it should error out., like below:
ASSERT_EQUAL(test1(42), true, "cache-friendly");
I tried:
static_assert(std::is_fundamental<decltype(expected)::value)
But it doesn't work as it won't error out even if I pass in the function call as first paramter expected, because its return value is a fundamental type.
is there a way to static assert and error out if the order of paramters are not as expected?
FYI - the macro implementation:
static int CaseNum = 0;
#define ASSERT_BASE(expected, actualFunc, desc) \
std::chrono::steady_clock clock; \
auto start = clock.now(); \
auto actual = actualFunc; \
auto elapsedTime = clock.now() - start; \
auto timeInUS = std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime).count(); \
cout << boolalpha << "CASE " << ++CaseNum << ": " << "EXPECTED=" << expected << "; ACTUAL=" << actual << "; elapsedTime=" << timeInUS << "us" << " --- " << desc << endl; \
#define ASSERT_EQUAL(expected, actualFunc, desc) \
{ \
ASSERT_BASE(expected, actualFunc, desc) \
assert(expected == actual); \
}
#define ASSERT_NEAR(expected, actualFunc, desc) \
{ \
ASSERT_BASE(expected, actualFunc, desc) \
assert(fabs(expected - actual) < 0.0001); \
}
This is not really possible by inspecting the expression within the language, since C++ is continually expanding the horizons of what is allowed in a constant-expression. You could check that expected can be used as a constant expression by e.g. using it to initialize a constinit variable, but this could result in a false negative if the test function is constexpr, which it might well be.
Instead, you could perform textual introspection using the preprocessor "stringification" operator:
static_assert(std::string_view{#expected}.find('(') == std::string_view::npos, "expected must be a literal");
Example.
I might suggest another approach (without MACRO) to enforce that second argument is a function/callable (instead of result of function):
template <typename T, typename F>
void AssertRun(T expected, F f, const std::string& descr)
{
static int CaseNum = 0;
std::chrono::steady_clock clock;
const auto start = clock.now();
auto actual = f();
const auto elapsedTime = clock.now() - start;
const auto timeInUS =
std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime).count();
std::cout << boolalpha << "CASE " << ++CaseNum << ": "
<< "EXPECTED=" << expected
<< "; ACTUAL=" << actual
<< "; elapsedTime=" << timeInUS << "us"
<< " --- " << desc << std::endl;
return actual;
}
template <typename T, typename F>
void AssertEqual(T expected, F f, const std::string& descr)
{
auto actual = AssertRun(expected, f, descr);
assert(expected == actual);
}
and then
AssertEqual(true, []{ return test1(42); }, "cache-friendly");
AssertEqual(test1(42), []{ return true; }, "cache-friendly"); would still be possible, but seems less probable to confound.
I have the following:
void print_str(std::shared_ptr<std::string> str) {
std::cout << str->c_str() << std::endl;
}
int main() {
auto str = std::make_shared<std::string>("Hello");
std::function<void()> f = std::bind(print_str, str);
f(); // correctly print: Hello
return 0;
}
I think the type of std::bind(print_str, str) is std::function<void(std::shared_ptr<std::string>)>, but the code above is correctly running. Is there any trick in std::bind?
env: centos, gcc82
What std::bind does is correct. It uses the value you provided (str) for the call to print_str. So you don't need to specify it anymore and will always be replaced by the bound value.
#include <iostream>
#include <functional>
int sum(int value1, int value2) {
return value1 + value2;
}
int main() {
std::function<int(int, int)> f1 = std::bind(sum, std::placeholders::_1, std::placeholders::_1);
std::function<int(int)> f2 = std::bind(sum, 10, std::placeholders::_1);
std::function<int()> f3 = std::bind(sum, 100, 200);
std::function<int(int)> f4 = std::bind(sum, std::placeholders::_1, 200);
int a = 1;
int b = 2;
std::cout << "the sum of " << a << " and " << b << " is: " << f1(a, b) << std::endl;
std::cout << "the sum of " << 10 << " and " << b << " is: " << f2(b) << std::endl;
std::cout << "the sum of " << 100 << " and " << 200 << " is: " << f3() << std::endl;
std::cout << "the sum of " << 200 << " and " << b << " is: " << f4(b) << std::endl;
return 0;
}
output:
the sum of 1 and 2 is: 2
the sum of 10 and 2 is: 12
the sum of 100 and 200 is: 300
the sum of 200 and 2 is: 202
f1 binds no values but placeholders and returns an int(int, int) like function
f2 binds one value and one placeholder and returns an int(int) like function
f3 binds two values and no placeholder and returns an int() like function
f4 is like f2 except that the place holder is now the first parameter instead of the second one.
Your code falls into the f3 case.
I think the type of std::bind(print_str, str) is std::function<void(std::shared_ptr<std::string>)>
No, the type of std::bind(print_str, str) is an unspecified functor type, something like
class binder
{
void(*f)(std::shared_ptr<std::string>);
std::shared_ptr<std::string> p;
public:
template<typename... Args>
void operator()(Args... ) { f(p); }
};
Note that this is callable with any arguments or none.
What you are experiencing here is correct and is precisely doing what std::bind was designed for.
Simply speaking:
It turns a function taking n parameters into a function taking m parameters (where n >= m).
In your particular case, you give it a function taking one parameter and get back a function taking zero parameters. This new function will internally call print_str and always pass str as argument.
Side note:
Since there are lambdas in C++11, std::bind is sort of redundant.
What you are doing is exactly equivalent to this:
void print_str(std::shared_ptr<std::string> str) {
std::cout << str->c_str() << std::endl;
}
int main() {
auto str = std::make_shared<std::string>("Hello");
std::function<void()> f = [=]() { print_str(str); };
f(); // correctly print: Hello
return 0;
}
This hopefully also helps understanding what std::bind does behind the scenes.
I am trying to write a function that accepts any other arbitrary function as an input and times it, then returns the results of that function. I have been at it for a couple hours now and think I'm getting pretty close, but I still can't quite figure out how to get it to compile.
This is what I have so far:
// Some arbitrary function to pass to the timer
int DoSomething(int x, double y)
{
// Does something with x and y
return 0;
}
// Function used to time another function and return its result
template <typename T, typename Function, typename... Args>
T GetRuntime(const std::string& name, const Function& function, Args&&... args)
{
std::cout << name << " started..." << std::endl;
auto start = std::chrono::high_resolution_clock::now();
T result = function(std::forward<Args>(args)...);
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
std::cout << name << " complete - duration: " << duration.count() << " milliseconds." << std::endl;
return result;
}
int main()
{
// Doesn't compile
int output = GetRuntime("DoSomething", DoSomething, 42, 42.42);
}
Am I approaching this the right way? If so, what do I need to change to make this work? If not, what is a better way to approach this problem?
The problem here is that T is not deducable in your function. The value you are assigning the return to does not participate in template parameter deduction. To use it as is, you need to specify the return type by using
int output = GetRuntime<int>("DoSomething", DoSomething, 42, 42.42);
^^^ specify T is an int
but we can make this better by using auto for the return type of the function. Using that turns the function into
template <typename Function, typename... Args>
auto GetRuntime(const std::string& name, const Function& function, Args&&... args)
{
std::cout << name << " started..." << std::endl;
auto start = std::chrono::high_resolution_clock::now();
auto result = function(std::forward<Args>(args)...);
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
std::cout << name << " complete - duration: " << duration.count() << " milliseconds." << std::endl;
return result;
}
The compiler cannot infer T. It's a template argument but there is nothing in your parameter to deduce it.
However, You can use std::invoke_result to get the return type of your function:
template <typename Function, typename... Args, typename R = std::invoke_result_t<Function, Args...>>
auto GetRuntime(const std::string& name, const Function& function, Args&&... args) -> R {
// ...
}
NathanOliver already gave a correct answer. This is just an answer on how to make the function more generic. It works with void functions as well and doesn't do any logging. It returns a tuple containing the duration and the return value of the passed function. If the passed function returns void, it just returns the duration directly (no tuple.)
(This is all C++17.)
// Need these includes in addition to what you already include.
#include <tuple>
#include <type_traits>
template <typename Function, typename... Args>
auto GetRuntime(const Function& function, Args&&... args)
{
auto start = std::chrono::high_resolution_clock::now();
if constexpr (std::is_same_v<decltype(function(args...)), void>) {
function(std::forward<Args>(args)...);
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - start);
} else {
auto&& func_result{function(std::forward<Args>(args)...)};
return std::make_tuple(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - start),
std::forward<decltype(function(args...))>(func_result));
}
}
You can use that directly to get the duration:
auto duration = GetRunTime(void_func, arg1, arg2);
cout << "Took " << duration.count() << "ms.\n";
With non-void functions, you use structured bindings:
auto [duration, int_val] = GetRunTime(int_func, arg1, arg2);
cout << "Took " << duration.count() << "ms and returned " << int_val << '\n';
Your original logging functionality can be implemented as a wrapper around GetRuntime(). The return type of the logging function is going to be the same as that of the passed function:
template <typename Function, typename... Args>
auto LogRuntime(const std::string& name, const Function& function, Args&&... args)
{
std::cout << name << " starting..." << std::endl;
auto result = GetRuntime(function, std::forward<Args>(args)...);
std::cout << name << " complete - duration: ";
constexpr auto is_void = std::is_same_v<decltype(function(args...)), void>;
if constexpr (is_void) {
std::cout << result.count();
} else {
std::cout << std::get<0>(result).count();
}
std::cout << " milliseconds.\n";
if constexpr (!is_void) {
return std::get<1>(result);
}
}
I was using a templatized meter function (see below) to measure the elapsed time for functions.
Then I also wanted to use it for constructors.
As far as I know there is no way of passing a type as a function parameter directly. So I came up with this workaround, passing it solely as the template parameter (minimal example):
template <typename T, typename ... P>
auto meter(T t, P ... p) {
auto t1 = high_resolution_clock::now();
t(p...);
auto t2 = high_resolution_clock::now();
auto dif = t2-t1;
return duration_cast<microseconds>(dif);
}
template <typename T, typename ... P>
auto meter(P ... p) {
auto t1 = high_resolution_clock::now();
auto t = T(p...);
auto t2 = high_resolution_clock::now();
auto dif = t2-t1;
return duration_cast<microseconds>(dif);
}
int main() {
auto d = meter(g, 1.0, 20.0); //meter the function call g(1.0, 20.0)
std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
d = meter(complex_obj{2}); //meter () operator of complex_obj, assuming complex_obj{int} is trivial;
std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
d = meter<complex_obj>(); //meter constructor complex_obj();
std::cout << "Ellapsed time: " << d.count() << " microseconds\n";
}
Trying this got me thinking. Is there a general/consistent way of rewriting this, as to apply to any kind of computation (not just constructor, but maybe even other operators like (obj1 < obj2)? I noticed, that I was already (accidentally) supporting the () operator of structs.
Sorry if this question became to broad, my main question is, if there is a way to unite the syntax of meter call, for functions and constructors alike.
You can wrap the code to be measured inside a lambda (since C++11):
#include <chrono>
#include <iostream>
template<class F>
auto meter(F&& f) {
auto t1 = std::chrono::high_resolution_clock::now();
f();// <-- operator() of the lambda
auto t2 = std::chrono::high_resolution_clock::now();
auto dif = t2-t1;
return std::chrono::duration_cast<std::chrono::microseconds>(dif);
}
void g(double x, double y) {
std::cout << "g(" << x << ", " << y << ")\n";
}
int main() {
double x = 1.0;
auto d = meter([&] {
// This comment is inside the *body* of the lambda.
// Code of the {body} is executed upon `operator()`.
g(x, 20.0);// note that you can use `x` here thanks to the capture-default `[&]`
});
std::cout << "time: " << d.count() << " ms\n";
}
However you encapsulate the actual function call, it'll probably be for the best if you make your meter function return the value returned by the function it's measuring to make it possible to chain calls - but still with the possibility to check how long time each individual call took afterwards. This makes it theoretically possible for RVO / copy elision to kick in and therefor not slow down the code as much. Example:
#include <chrono>
#include <iostream>
#include <thread> // for debug sleeps only
using namespace std::chrono;
template<typename D, typename F, typename... P>
auto meter(D& dur, F func, P&&... params) {
auto start = high_resolution_clock::now();
auto retval = func(std::forward<P>(params)...);
// put duration in the duration reference
dur = duration_cast<D>(high_resolution_clock::now() - start);
// and return func()'s return value
return retval;
}
namespace m {
double add(double a, double b) {
std::this_thread::sleep_for(milliseconds(10));
return a + b;
}
double sub(double a, double b) {
std::this_thread::sleep_for(milliseconds(11));
return a - b;
}
double mul(double a, double b) {
std::this_thread::sleep_for(milliseconds(12));
return a * b;
}
double div(double a, double b) {
std::this_thread::sleep_for(milliseconds(13));
return a / b;
}
} // namespace m
int main() {
milliseconds Add, Sub, Mul, Div;
// chaining calls for this calculation:
// (1000 / (100 * (4.3 - (1.1+2.2))))
auto result = meter(Div, m::div,
1000.0, meter(Mul, m::mul,
100.0, meter(Sub, m::sub,
4.3, meter(Add, m::add,
1.1, 2.2)
)
)
);
std::cout << "Add: " << Add.count() << " ms.\n";
std::cout << "Sub: " << Sub.count() << " ms.\n";
std::cout << "Mul: " << Mul.count() << " ms.\n";
std::cout << "Div: " << Div.count() << " ms.\n";
std::cout << result << "\n";
}
Probable output:
Add: 10 ms.
Sub: 11 ms.
Mul: 12 ms.
Div: 13 ms.
10
How to return a tuple from a function that takes a list of variadic template? The tuple types should be the same to the argument types.
template <typename ... T, typename D>
std::tuple<T...> foo(D&& Duration) {
// How to do this?
if constexpr(sizeof... (args) > 0) {
...
}
// What to return?
}
The idea is to use C++17 structure binding so I can do something like this:
auto var = std::chrono::seconds(19874);
auto [h, m] = foo<std::chrono::hours, std::chrono::minutes> (var);
auto [m, s, ms] = foo<std::chrono::minutes, std::chrono::seconds, std::chrono::milliseconds>(var);
For both cases, the sum (h:m or m:s:ms) should be 19874 seconds.
This code should do what you ask. It constructs the tuple using an initializer list of calls to chrono_extract for each of the ouptut types. The initializer list parameters are processed in order according to the standard, so this shouldn't be sensitive to any reordering even though it might look like it from the use of the commas. The chrono_extract function casts the input duration to the type of the output duration, then subtracts the output from the input (which is passed by reference, so its value will be reflected in chrono_components). In order to correctly run the algorithm, we need to find the smallest time component in the list, and run all calculations at the smallest resolution (otherwise there will be duration casting errors). Also provided is an assertion to make sure that the return duration types are in decreasing order because the function will not operate correctly if they are in a different order. One possible enhancement that could be added is to also return a "remainder" in the tuple (that is the same as the input type) if there is anything left over from the decomposition (as in the first test case in the code below).
#include <chrono>
#include <iostream>
#include <tuple>
#include <type_traits>
template <typename lhs_t, typename rhs_t, typename... other_ts>
constexpr void assert_decreasing_ratios() {
static_assert(std::ratio_greater_v<typename lhs_t::period,
typename rhs_t::period>,
"Periods are non-decreasing.");
if constexpr (sizeof...(other_ts)) {
assert_decreasing_ratios<rhs_t, other_ts...>();
}
}
template <typename return_duration_t, typename input_duration_t>
return_duration_t chrono_extract(input_duration_t& value) {
auto extracted = std::chrono::duration_cast<return_duration_t>(value);
value -= extracted;
return extracted;
}
template <typename... return_ts, typename duration_t>
std::tuple<return_ts...> chrono_components(duration_t value) {
assert_decreasing_ratios<return_ts...>();
using smallest_t = std::tuple_element_t<sizeof...(return_ts) - 1,
std::tuple<return_ts...>>;
auto small_value = std::chrono::duration_cast<smallest_t>(value);
return {chrono_extract<return_ts>(small_value)...};
}
int main()
{
std::chrono::seconds before(19874);
{
auto [h, m] = chrono_components<std::chrono::hours,
std::chrono::minutes>(before);
std::chrono::seconds after(h + m);
std::cout << h.count() << " hours "
<< m.count() << " minutes = "
<< after.count() << " seconds\n";
}
{
auto [m, s, ms] = chrono_components<std::chrono::minutes,
std::chrono::seconds,
std::chrono::milliseconds>(before);
auto after =
std::chrono::duration_cast<std::chrono::seconds>(m + s + ms);
std::cout << m.count() << " minutes "
<< s.count() << " seconds "
<< ms.count() << " milliseconds = "
<< after.count() << " seconds\n";
}
}
Output
5 hours 31 minutes = 19860 seconds
331 minutes 14 seconds 0 milliseconds = 19874 seconds
The first line shows that the seconds value has been truncated because it's not an exact number of minutes.