在c ++中以优雅的方式进行计时

时间:2015-07-13 19:34:23

标签: c++ timing

我感兴趣的是计算自由函数或成员函数(模板与否)的执行时间。调用TheFunc有问题的函数,其调用是

TheFunc(/*parameters*/);

ReturnType ret = TheFunc(/*parameters*/);

当然,我可以按如下方式包装这些函数调用:

double duration = 0.0 ;
std::clock_t start = std::clock();
TheFunc(/*parameters*/);
duration = static_cast<double>(std::clock() - start) / static_cast<double>(CLOCKS_PER_SEC);

double duration = 0.0 ;
std::clock_t start = std::clock();
ReturnType ret = TheFunc(/*parameters*/);
duration = static_cast<double>(std::clock() - start) / static_cast<double>(CLOCKS_PER_SEC);

但是我想做一些比这更优雅的事情,即(从现在起我会坚持使用void返回类型),如下所示:

Timer thetimer ;
double duration = 0.0;
thetimer(*TheFunc)(/*parameters*/, duration);

其中Timer是我想设计的一些时序类,这将允许我编写前面的代码,这样在前面代码的最后一行的exectution之后,double duration将包含执行时间< / p>

TheFunc(/*parameters*/);

但我不知道如何做到这一点,也不是我的目标语法/解决方案是最佳的......

4 个答案:

答案 0 :(得分:7)

使用可变参数模板,您可以执行以下操作:

template <typename F, typename ... Ts>
double Time_function(F&& f, Ts&&...args)
{
    std::clock_t start = std::clock();
    std::forward<F>(f)(std::forward<Ts>(args)...);
    return static_cast<double>(std::clock() - start) / static_cast<double>(CLOCKS_PER_SEC);
}

答案 1 :(得分:5)

我非常喜欢boost::cpu_timer::auto_cpu_timer,当我无法使用提升时,我只是自己破解:

#include <cmath>
#include <string>
#include <chrono>
#include <iostream>

class AutoProfiler {
 public:
  AutoProfiler(std::string name)
      : m_name(std::move(name)),
        m_beg(std::chrono::high_resolution_clock::now()) { }
  ~AutoProfiler() {
    auto end = std::chrono::high_resolution_clock::now();
    auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end - m_beg);
    std::cout << m_name << " : " << dur.count() << " musec\n";
  }
 private:
  std::string m_name;
  std::chrono::time_point<std::chrono::high_resolution_clock> m_beg;
};

void foo(std::size_t N) {
  long double x {1.234e5};
  for(std::size_t k = 0; k < N; k++) {
    x += std::sqrt(x);
  }  
}


int main() {
  {
    AutoProfiler p("N = 10");
    foo(10);
  }

  {
    AutoProfiler p("N = 1,000,000");
    foo(1000000);
  }

}

此计时器适用于RAII。在范围内构建对象时,将时间点存储在该时间点。当您离开范围(即,在相应的})时,计时器首先存储时间点,然后计算刻度数(您可以将其转换为人类可读的持续时间),最后将其打印到屏幕

当然,boost::timer::auto_cpu_timer比我的简单实现更精细,但我经常发现我的实现对我的目的来说已经足够了。

在我的电脑中运行示例:

$ g++ -o example example.com -std=c++14 -Wall -Wextra
$ ./example
N = 10 : 0 musec
N = 1,000,000 : 10103 musec

修改

我非常喜欢@ Jarod42建议的实现。我对它进行了一些修改,以便为所需的&#34;单位提供一些灵活性。输出。

默认返回经过的微秒数(一个整数,通常为std::size_t),但您可以请求输出处于您选择的任何持续时间内。

我认为它比我之前建议的方法更灵活,因为现在我可以做其他的事情,比如测量并将它们存储在容器中(就像我在示例中所做的那样)。

感谢@ Jarod42的灵感。

#include <cmath>
#include <string>
#include <chrono>
#include <algorithm>
#include <iostream>

template<typename Duration = std::chrono::microseconds,
         typename F,
         typename ... Args>
typename Duration::rep profile(F&& fun,  Args&&... args) {
  const auto beg = std::chrono::high_resolution_clock::now();
  std::forward<F>(fun)(std::forward<Args>(args)...);
  const auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - beg).count();
}

void foo(std::size_t N) {
  long double x {1.234e5};
  for(std::size_t k = 0; k < N; k++) {
    x += std::sqrt(x);
  }
}

int main() {
  std::size_t N { 1000000 };

  // profile in default mode (microseconds)
  std::cout << "foo(1E6) takes " << profile(foo, N) << " microseconds" << std::endl;

  // profile in custom mode (e.g, milliseconds)
  std::cout << "foo(1E6) takes " << profile<std::chrono::milliseconds>(foo, N) << " milliseconds" << std::endl;

  // To create an average of `M` runs we can create a vector to hold
  // `M` values of the type used by the clock representation, fill
  // them with the samples, and take the average
  std::size_t M {100};
  std::vector<typename std::chrono::milliseconds::rep> samples(M);
  for(auto & sample : samples) {
    sample = profile(foo, N);
  }
  auto avg = std::accumulate(samples.begin(), samples.end(), 0) / static_cast<long double>(M);
  std::cout << "average of " << M << " runs: " << avg << " microseconds" << std::endl;
}

输出(使用g++ example.cpp -std=c++14 -Wall -Wextra -O3编译):

foo(1E6) takes 10073 microseconds
foo(1E6) takes 10 milliseconds
average of 100 runs: 10068.6 microseconds

答案 2 :(得分:1)

我是使用RAII包装器的粉丝。

以下示例有点冗长,但它更灵活,因为它适用于任意范围,而不仅限于单个函数调用:

class timing_context {
public:
  std::map<std::string, double> timings;
};

class timer {
public:
  timer(timing_context& ctx, std::string name)
    : ctx(ctx),
      name(name),
      start(std::clock()) {}

  ~timer() {
    ctx.timings[name] = static_cast<double>(std::clock() - start) / static_cast<double>(CLOCKS_PER_SEC);
  }

  timing_context& ctx;
  std::string name;
  std::clock_t start;
};

timing_context ctx;

int main() {
  timer_total(ctx, "total");
  {
    timer t(ctx, "foo");
    // Do foo
  }

  {
    timer t(ctx, "bar");
    // Do bar
  }
  // Access ctx.timings
}

缺点是你最终会得到很多只会破坏计时对象的范围。

这可能会或可能不会满足您的要求,因为您的请求有点模糊,但它说明了如何使用RAII语义可以为一些非常好的可重用和干净的代码。它可能会被修改为看起来好多了!

答案 3 :(得分:1)

你可以用MatLab方式做到这一点。这是非常老派,但简单往往很好:

    tic();
    a = f(c);
    toc(); //print to stdout, or
    auto elapsed = toc(); //store in variable

tic()toc()可以处理全局变量。如果这还不够,可以使用一些宏观魔法创建局部变量:

    tic(A);
    a = f(c);
    toc(A);