【问题标题】:A timer for arbitrary functions任意功能的定时器
【发布时间】:2014-06-28 15:39:42
【问题描述】:

我尝试构建一个可以测量任意类型函数的执行时间的函数模板。到目前为止,这是我尝试过的:

#include <chrono>
#include <iostream>
#include <type_traits>
#include <utility>

// Executes fn with arguments args and returns the time needed
// and the result of f if it is not void
template <class Fn, class... Args>
auto timer(Fn fn, Args... args)
    -> std::pair<double, decltype(fn(args...))> {
  static_assert(!std::is_void<decltype(fn(args...))>::value,
                "Call timer_void if return type is void!");
  auto start = std::chrono::high_resolution_clock::now();
  auto ret = fn(args...);
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed_seconds = end - start;
  return { elapsed_seconds.count(), ret };
}

// If fn returns void, only the time is returned
template <class Fn, class... Args>
double timer_void(Fn fn, Args... args) {
  static_assert(std::is_void<decltype(fn(args...))>::value,
                "Call timer for non void return type");
  auto start = std::chrono::high_resolution_clock::now();
  fn(args...);
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed_seconds = end - start;
  return elapsed_seconds.count();
}

int main () {
    //This call is ambigous if the templates have the same name
    std::cout << timer([](double a, double b){return a*b;},1,2).first;
}

请注意,对于void(...) 函数,我必须有一个具有不同名称的函数。有没有办法摆脱第二个功能?

(我一开始做的对吗?)

【问题讨论】:

  • 不,这是不正确的(温和的) - 你应该制作一个简单的工作测试用例,其中包含所有包含和主要的
  • 未经测试的代码就是不工作的代码
  • @YePhIcK 它适用于示例,但这并不意味着它是正确的。
  • 我认为你应该在codereview.stackexchange.com 发帖。
  • @RSahu 我认为它属于这里,因为它不像我想要的那样工作(即不需要两个不同的名称)。我不会就我的风格等寻求建议(尽管当然总是受欢迎的)。编辑:重新阅读 codereview 的规则,这似乎是主题。所以如果那里更合适,我可以搬家吗?

标签: c++ templates c++11 variadic-templates chrono


【解决方案1】:

您可以使用enable_if 或标签调度。在这种情况下,Enable_if 似乎是更快的方法:

#include <type_traits>

template <class Fn, class... Args>
auto timer(Fn fn, Args && ... args) -> typename std::enable_if< 
    // First template argument is the enable condition
    !std::is_same< 
            decltype( fn( std::forward<Args>(args) ... )), 
            void >::value,
    // Second argument is the actual return type
    std::pair<double, decltype(fn(std::forward<Args>(args)...))> >::type
{
   // Implementation for the non-void case
}

template <class Fn, class... Args>
auto timer(Fn fn, Args &&... args) -> typename std::enable_if< 
    std::is_same< 
            decltype( fn( std::forward<Args>(args) ... )), 
            void >::value,
    double>::type
{
   // Implementation for void case
}

您还应该使用完美转发将参数传递给被调用的函数:

 auto timer(Fn fn, Args && ... args) // ...
                      ~~~^   

当你调用函数时:

 auto ret = fn( std::forward<Args>(args)...);

Demo。请注意,这适用于函数、lambda 和可调用对象;几乎所有东西都带有operator()

从设计的角度来看,我认为返回 std::pair 没有问题。由于 C++11 具有 std::tie,因此返回 pair/tuple 是从函数返回多个结果的合法方式。我会继续说,为了在 void 情况下保持一致性,您应该返回一个只有一个元素的元组。

【讨论】:

  • 您忘记将 void 案例的返回类型从 pair 更改为 double。但到目前为止,我最喜欢这个解决方案。
  • 备注:通过在两种情况下都返回一个tuple,我可以使用std::get&lt;0&gt;来访问时间而不用担心返回类型。我可以使用std::is_void 而不是std::is_same 来获得更简洁的代码。 std::forward 的模板参数可以自动推导出来。总而言之,正是我想要的。
  • @BaummitAugen 酷我不记得is_void。自动扣减std::forward的参数你错了;你需要它,否则它只会每次都推断出左值引用。
【解决方案2】:

在这种情况下,我会将持续时间作为对函数调用包装器的引用传递:

#include <chrono>
#include <iostream>
#include <thread>

template <typename Duration, class Fn, class... Args>
auto call(Duration& duration, Fn fn, Args... args) -> decltype(fn(args...)) {

    using namespace std::chrono;

    struct DurationGuard {
        Duration& duration;
        high_resolution_clock::time_point start;
        DurationGuard(Duration& duration)
        :   duration(duration),
            start(high_resolution_clock::now())
        {}
        ~DurationGuard() {
            high_resolution_clock::time_point end = high_resolution_clock::now();
            duration = duration_cast<Duration>(end - start);
        }
    };

    DurationGuard guard(duration);
    return fn(args...);
}

void f() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

int g() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
}

int main () {

    using namespace std::chrono;

    duration<double> s;
    call(s, f);
    std::cout << s.count() << '\n';

    milliseconds ms;
    int n = call(ms, g);
    std::cout << ms.count() << ", " << n << '\n';
}

你可以把它封装在一个类中:

#include <chrono>
#include <iostream>
#include <thread>

template <typename Duration = std::chrono::duration<double>>
class InvokeDuration
{
    public:
    template<typename Fn, class... Args>
    auto operator () (Fn fn, Args... args) -> decltype(fn(args...)) {
        using namespace std::chrono;
        struct Guard {
            Duration& duration;
            high_resolution_clock::time_point start;
            Guard(Duration& duration)
            :   duration(duration),
                start(high_resolution_clock::now())
            {}
            ~Guard() {
                high_resolution_clock::time_point end = high_resolution_clock::now();
                duration = duration_cast<Duration>(end - start);
            }
        };
        Guard guard(m_duration);
        return fn(args...);
    }

    const Duration& duration() const { return m_duration; }
    typename Duration::rep count() const { return m_duration.count(); }

    private:
    Duration m_duration;
};

void f() {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

int g(int n) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    return n;
}

int main () {
    InvokeDuration<> invoke;
    invoke(f);
    std::cout << invoke.count() << '\n';
    int n = invoke(g, 42);
    std::cout << invoke.count() << ", " << n << '\n';
}

注意:从函数调用返回 void 定义明确:void a() { return b(); }void b()

【讨论】:

  • 我个人不太喜欢输入参数,但它的类型也被推断出来了。 +1
【解决方案3】:

只是超载它。此外,您应该如下更改函数签名。 Live code.

template <typename R, typename... Args>
auto timer(R (*fn)(Args...), Args... args) -> std::pair<double, R>
{
    //...

    auto ret = fn(args...);

    //...

    return { elapsed_seconds.count(), ret };
}

对于void

template <typename... Args>
auto timer(void (*fn)(Args...), Args... args) -> double
{
    //...

    fn(args...);

    //...

    return elapsed_seconds.count();
}

但它不适用于 lambdas。

 

有一个workaround 用于非捕获 lambda 函数(阻止泛化)

template <typename Function>
struct function_traits
  : public function_traits<decltype(&Function::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
  typedef ReturnType (*pointer)(Args...);
  typedef std::function<ReturnType(Args...)> function;
};

template <typename Function>
typename function_traits<Function>::pointer
to_function_pointer (const Function& lambda)
{
  return static_cast<typename function_traits<Function>::pointer>(lambda);
}

然后你可以像这样传递 lambda:

timer(to_function_pointer([](){

    // Lambda function

}));

【讨论】:

  • 你的函数签名一般比我的好还是只是为了重载?
  • 更适合重载和模板类型推导。我还添加了一个实时工作代码。
  • 遗憾的是,这似乎不适用于 lambdas (live)。
  • 是的,我已经注意到并在答案中提到了它。是的,lambda 对他们来说太疯狂了。我已经为 lambdas 写了一个解决方法,你可以看到它。
  • 如果你要重载的方式,使用 sfinae 或标签调度比这更好。也不是每个 lambda 都可以隐式转换为函数指针。
【解决方案4】:

C++14 通用 lambda 消除了使用模板的需要。我在 Effective Modern C++ 中看到的代码 sn-p 证明了这一点:

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params)
    {
        start timer; 
        std::forward<decltype(func)>(func)(
            std::forward<decltype(params)>(params)...); 
        stop timer and record elapsed time; 
    };

【讨论】:

  • 看起来不错,但这并不能解决从函数返回值的挑战
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
相关资源
最近更新 更多