轻松测量经过时间

403

我正在尝试使用time()来测量我的程序的各个时间点。

我不明白的是为什么“before”和“after”的值是相同的?我知道这不是最好的程序性能分析方法,但我只是想看一下某些操作需要多长时间。

printf("**MyProgram::before time= %ld\n", time(NULL));

doSomthing();
doSomthingLong();

printf("**MyProgram::after time= %ld\n", time(NULL));
我已经尝试过:
struct timeval diff, startTV, endTV;

gettimeofday(&startTV, NULL); 

doSomething();
doSomethingLong();

gettimeofday(&endTV, NULL); 

timersub(&endTV, &startTV, &diff);

printf("**time taken = %ld %ld\n", diff.tv_sec, diff.tv_usec);

如何解读** time taken = 0 26339的结果?这是否意味着26,339纳秒等于26.3毫秒?

** time taken = 4 45025又是怎样的含义?它是否代表了4秒又25毫秒?


10
我不理解这个问题。当然,这些值是不同的。由于经过了一段时间,所以time()会返回一个不同的值。 - Thomas
1
你说的“我不明白的是为什么before和after的值不同”是什么意思?你正在使用time(NULL)获取当前时间(自1970年1月1日以来的秒数)...第二次调用它将在第一次之后N秒,因此...不同(除非你所做的事情不需要一秒钟完成...在这种情况下,它将与第一个相同)。 - Brian Roach
1
你能告诉我们它打印了什么,如果用秒表、挂钟(或日历)计时,需要多长时间吗? - Matt Curtis
4
抱歉,我的意思是这两个值是相同的。我打错了问题。 - hap497
2
请参见此主题:https://dev59.com/MXVC5IYBdhLWcg3wfxQ8 - default
显示剩余7条评论
26个回答

8
#include <ctime>
#include <cstdio>
#include <iostream>
#include <chrono>
#include <sys/time.h>
using namespace std;
using namespace std::chrono;

void f1()
{
  high_resolution_clock::time_point t1 = high_resolution_clock::now();
  high_resolution_clock::time_point t2 = high_resolution_clock::now();
  double dif = duration_cast<nanoseconds>( t2 - t1 ).count();
  printf ("Elasped time is %lf nanoseconds.\n", dif );
}

void f2()
{
  timespec ts1,ts2;
  clock_gettime(CLOCK_REALTIME, &ts1);
  clock_gettime(CLOCK_REALTIME, &ts2);
  double dif = double( ts2.tv_nsec - ts1.tv_nsec );
  printf ("Elasped time is %lf nanoseconds.\n", dif );
}

void f3()
{
  struct timeval t1,t0;
  gettimeofday(&t0, 0);
  gettimeofday(&t1, 0);
  double dif = double( (t1.tv_usec-t0.tv_usec)*1000);
  printf ("Elasped time is %lf nanoseconds.\n", dif );
}
void f4()
{
  high_resolution_clock::time_point t1 , t2;
  double diff = 0;
  t1 = high_resolution_clock::now() ;
  for(int i = 1; i <= 10 ; i++)
  {
    t2 = high_resolution_clock::now() ;
    diff+= duration_cast<nanoseconds>( t2 - t1 ).count();
    t1 = t2;
  }
  printf ("high_resolution_clock:: Elasped time is %lf nanoseconds.\n", diff/10 );
}

void f5()
{
  timespec ts1,ts2;
  double diff = 0;
  clock_gettime(CLOCK_REALTIME, &ts1);
  for(int i = 1; i <= 10 ; i++)
  {
    clock_gettime(CLOCK_REALTIME, &ts2);
    diff+= double( ts2.tv_nsec - ts1.tv_nsec );
    ts1 = ts2;
  }
  printf ("clock_gettime:: Elasped time is %lf nanoseconds.\n", diff/10 );
}

void f6()
{
  struct timeval t1,t2;
  double diff = 0;
  gettimeofday(&t1, 0);
  for(int i = 1; i <= 10 ; i++)
  {
    gettimeofday(&t2, 0);
    diff+= double( (t2.tv_usec-t1.tv_usec)*1000);
    t1 = t2;
  }
  printf ("gettimeofday:: Elasped time is %lf nanoseconds.\n", diff/10 );
}

int main()
{
  //  f1();
  //  f2();
  //  f3();
  f6();
  f4();
  f5();
  return 0;
}

5

C++ std::chrono有明显的跨平台优势。然而,与POSIX clock_gettime()相比,它也引入了显著的开销。 在我的Linux系统上,所有std::chrono::xxx_clock::now()函数大致执行相同:

std::chrono::system_clock::now()
std::chrono::steady_clock::now()
std::chrono::high_resolution_clock::now()

尽管 POSIX 的 clock_gettime(CLOCK_MONOTONIC, &time) 理应与 steady_clock::now() 相同,但前者速度快于后者超过3倍!
以下是我的测试,为了完整性。
#include <stdio.h>
#include <chrono>
#include <ctime>

void print_timediff(const char* prefix, const struct timespec& start, const 
struct timespec& end)
{
    double milliseconds = end.tv_nsec >= start.tv_nsec
                        ? (end.tv_nsec - start.tv_nsec) / 1e6 + (end.tv_sec - start.tv_sec) * 1e3
                        : (start.tv_nsec - end.tv_nsec) / 1e6 + (end.tv_sec - start.tv_sec - 1) * 1e3;
    printf("%s: %lf milliseconds\n", prefix, milliseconds);
}

int main()
{
    int i, n = 1000000;
    struct timespec start, end;

    // Test stopwatch
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < n; ++i) {
        struct timespec dummy;
        clock_gettime(CLOCK_MONOTONIC, &dummy);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    print_timediff("clock_gettime", start, end);

    // Test chrono system_clock
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < n; ++i)
        auto dummy = std::chrono::system_clock::now();
    clock_gettime(CLOCK_MONOTONIC, &end);
    print_timediff("chrono::system_clock::now", start, end);

    // Test chrono steady_clock
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < n; ++i)
        auto dummy = std::chrono::steady_clock::now();
    clock_gettime(CLOCK_MONOTONIC, &end);
    print_timediff("chrono::steady_clock::now", start, end);

    // Test chrono high_resolution_clock
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < n; ++i)
        auto dummy = std::chrono::high_resolution_clock::now();
    clock_gettime(CLOCK_MONOTONIC, &end);
    print_timediff("chrono::high_resolution_clock::now", start, end);

    return 0;
}

当我使用gcc7.2 -O3编译时,得到以下输出:

clock_gettime: 24.484926 milliseconds
chrono::system_clock::now: 85.142108 milliseconds
chrono::steady_clock::now: 87.295347 milliseconds
chrono::high_resolution_clock::now: 84.437838 milliseconds

3

正如其他人已经指出的那样,C标准库中的time()函数的分辨率不超过一秒。唯一完全可移植的C函数可能提供更好的分辨率是clock(),但它测量的是处理器时间而不是墙钟时间。如果一个人满足于限制自己使用POSIX平台(例如Linux),那么clock_gettime()函数是一个很好的选择。

自C++11以来,有更好的计时设施可用,这些设施提供更好的分辨率,并且在不同编译器和操作系统之间应该非常可移植。同样,boost::datetime库提供了良好的高分辨率计时类,应该高度可移植。

使用这些设施的一个挑战是通过查询系统时钟引入的时间延迟。通过使用clock_gettime()、boost::datetime和std::chrono进行实验,这种延迟很容易成为微秒级别的问题。因此,在测量代码任何部分的持续时间时,您需要考虑到存在大约这个大小的测量误差,或者尝试以某种方式纠正零误差。理想情况下,您可能希望收集函数所需时间的多个测量,并计算平均值或跨多个运行所需的最大/最小时间。
为了帮助解决所有这些可移植性和统计信息收集问题,我一直在开发cxx-rtimers库,该库可在Github上获得,并尝试为计时C++代码块、计算零误差以及从嵌入在代码中的多个计时器报告统计信息提供简单的API。如果您有C++11编译器,只需#include <rtimers/cxx11.hpp>,然后使用类似以下内容的东西:
void expensiveFunction() {
    static rtimers::cxx11::DefaultTimer timer("expensiveFunc");
    auto scopedStartStop = timer.scopedStart();
    // Do something costly...
}

程序退出时,您将获得一个时间统计摘要,写入到 std::cerr 中,例如:
Timer(expensiveFunc): <t> = 6.65289us, std = 3.91685us, 3.842us <= t <= 63.257us (n=731)

该函数显示平均时间、标准偏差、上下限以及调用此函数的次数。如果您想使用特定于Linux的计时函数,可以包含,或者如果您拥有Boost库但是使用较旧的C++编译器,则可以包含。这些计时器类的版本还可以从多个线程中收集统计定时信息。还有一些方法允许您估计两个立即连续查询系统时钟所关联的零误差。

3
< p > time(NULL) 函数调用将返回自1970年1月1日以来经过的秒数。也许你想要做的是取两个时间戳之间的差异:

size_t start = time(NULL);
doSomthing();
doSomthingLong();

printf ("**MyProgram::time elapsed= %lds\n", time(NULL) - start);

3
在Linux中,clock_gettime()是一个不错的选择。 您必须链接实时库(-lrt)。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

#define BILLION  1000000000L;

int main( int argc, char **argv )
  {
    struct timespec start, stop;
    double accum;

    if( clock_gettime( CLOCK_REALTIME, &start) == -1 ) {
      perror( "clock gettime" );
      exit( EXIT_FAILURE );
    }

    system( argv[1] );

    if( clock_gettime( CLOCK_REALTIME, &stop) == -1 ) {
      perror( "clock gettime" );
      exit( EXIT_FAILURE );
    }

    accum = ( stop.tv_sec - start.tv_sec )
          + ( stop.tv_nsec - start.tv_nsec )
            / BILLION;
    printf( "%lf\n", accum );
    return( EXIT_SUCCESS );
  }

2

从我所看到的,tv_sec 存储经过的秒数,而 tv_usec 分别存储了经过的微秒数。它们不是相互转换的。因此,必须将它们更改为适当的单位并相加以获取总经过时间。

struct timeval startTV, endTV;

gettimeofday(&startTV, NULL); 

doSomething();
doSomethingLong();

gettimeofday(&endTV, NULL); 

printf("**time taken in microseconds = %ld\n",
    (endTV.tv_sec * 1e6 + endTV.tv_usec - (startTV.tv_sec * 1e6 + startTV.tv_usec))
    );

2
我需要测量库中各个函数的执行时间。我不想用一个时间测量函数包装每个函数的每个调用,因为这样很丑陋并加深了调用栈。我也不想在每个函数的顶部和底部放置计时器代码,因为当函数可以早期退出或抛出异常时,这会造成混乱。所以我最终做的是制作一个计时器,使用它自己的生命周期来测量时间。

通过这种方式,我可以通过在问题代码块(函数或任何范围)的开头实例化其中一个对象,然后允许实例的析构函数在实例超出范围时测量自构建以来经过的时间,从而测量代码块花费的墙上时间。您可以在这里找到完整的示例,但结构非常简单:

template <typename clock_t = std::chrono::steady_clock>
struct scoped_timer {
  using duration_t = typename clock_t::duration;
  const std::function<void(const duration_t&)> callback;
  const std::chrono::time_point<clock_t> start;

  scoped_timer(const std::function<void(const duration_t&)>& finished_callback) :
      callback(finished_callback), start(clock_t::now()) { }
  scoped_timer(std::function<void(const duration_t&)>&& finished_callback) :
      callback(finished_callback), start(clock_t::now()) { }
  ~scoped_timer() { callback(clock_t::now() - start); }
};

当结构体超出作用域时,它会在提供的函数对象上调用您,以便您可以使用时间信息(打印或存储等)。如果您需要执行更复杂的操作,则甚至可以使用std :: bindstd :: placeholders将带有多个参数的回调函数。

以下是使用示例:

void test(bool should_throw) {
  scoped_timer<> t([](const scoped_timer<>::duration_t& elapsed) {
    auto e = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(elapsed).count();
    std::cout << "took " << e << "ms" << std::endl;
  });

  std::this_thread::sleep_for(std::chrono::seconds(1));

  if (should_throw)
    throw nullptr;

  std::this_thread::sleep_for(std::chrono::seconds(1));
}

如果你想更加谨慎,你也可以使用newdelete来明确启动和停止计时器,而不是依赖作用域来完成它。"最初的回答"

2

在内部,该函数将访问系统时钟,这就是为什么每次调用它时都会返回不同的值。通常,在非函数式语言中,函数中可能存在许多副作用和隐藏状态,您无法仅通过查看函数的名称和参数来看到它们。


1
我通常使用以下内容:
#include <chrono>
#include <type_traits>

using perf_clock = std::conditional<
    std::chrono::high_resolution_clock::is_steady,
    std::chrono::high_resolution_clock,
    std::chrono::steady_clock
>::type;

using floating_seconds = std::chrono::duration<double>;

template<class F, class... Args>
floating_seconds run_test(Func&& func, Args&&... args)
{
   const auto t0 = perf_clock::now();
   std::forward<Func>(func)(std::forward<Args>(args)...);
   return floating_seconds(perf_clock::now() - t0);
} 

除了我避免使用非稳定的时钟并使用浮点秒数作为持续时间之外,这与@nikos-athanasiou提出的相同。


2
在这个 typeswitch 中:通常 high_resolution_clocksystem_clocksteady_clock 的 typedef。因此,要跟踪 std::conditional,如果 is_steady 部分为 true,则选择 (typedef 为) steady_clockhigh_resolution_clock。如果为 false,则再次选择 steady_clock。从一开始就使用 steady_clock... - Nikos Athanasiou
@nikos-athanasiou 我完全同意5gon12eder的评论,即标准不需要“典型”情况,因此某些STL可能以不同的方式实现。我更喜欢我的代码更通用,不涉及实现细节。 - oliora
虽然不是必需的,但在__20.12.7.3__中明确说明:high_resolution_clock可能是system_clocksteady_clock的同义词。原因是:high_resolution_clock表示具有最短滴答周期的时钟,因此无论实现如何,它都有两个选择,即稳定或不稳定。无论我们做出什么选择,说实现将与另外两个时钟不同就像说我们有一个更好的稳定(或不稳定)时钟的实现,我们选择不使用(对于稳定或不稳定的时钟)。知道怎么做很好,知道为什么更好。 - Nikos Athanasiou
@nikos-athanasiou 我更喜欢百分之百的安全,特别是当这不会给我带来运行时开销和无法检测的编译时开销时。如果您愿意,可以依赖于“可能性”和假设。 - oliora
恰恰相反,我的朋友,是你依赖于“可能性”,但随你喜欢。如果你想要百分之百的确定性并继续这样写,那么你也应该找到一种方法,让你和代码用户避免在不同时钟的时间点上进行非可移植的混合(如果这种类型切换有意义,它将在不同平台上表现不同)。祝你玩得开心! - Nikos Athanasiou
@NikosAthanasiou,如您所见,没有任何time_point逃出了函数。返回类型是一个浮动秒数持续时间,它与时钟无关。因此,没有用户代码会依赖于实际选择的时钟。老实说,我不认为有任何继续这个讨论的必要了。 - oliora

1

它们是相同的,因为您的 doSomething 函数比计时器的粒度更快。请尝试:

printf ("**MyProgram::before time= %ld\n", time(NULL));

for(i = 0; i < 1000; ++i) {
    doSomthing();
    doSomthingLong();
}

printf ("**MyProgram::after time= %ld\n", time(NULL));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接