C程序的执行时间

282

我有一个C程序,旨在在多个处理器上并行运行。 我需要能够记录执行时间(可能从1秒到几分钟不等)。 我已经搜索了答案,但它们似乎都建议使用clock()函数,然后涉及计算程序所需的时钟数除以Clocks_per_second值。

我不确定如何计算Clocks_per_second值?

在Java中,我只需在执行前后获取当前时间(以毫秒为单位)。

在C中是否有类似的东西? 我已经查看了,但似乎找不到比一秒更好的分辨率。

我也知道性能分析器是一个选项,但正在寻求自己实现定时器。

谢谢


4
你使用/可用哪些操作系统/应用程序编程接口框架?只是纯C语言吗? - typo.pl
5
这是一个相当小的程序,只是普通的 C 代码。 - Roger
2
我在这个答案中详细介绍了如何实现一个可移植的解决方案:https://dev59.com/jHRC5IYBdhLWcg3wP-n5#37920181 - Alexander Saprykin
1
执行完整个函数所需的时间。https://dev59.com/C2Eh5IYBdhLWcg3wdjTt#40380118 - Abdullah Farweez
抱歉,投票数是“256”(完美数字..️)我来投票让它变成257.. https://en.wikipedia.org/wiki/256_(number)#In_computing - William Martens
相关:性能评估的惯用方式? - 基准测试很难,特别是对单个函数或循环进行有意义的微观基准测试。预热效应以及需要启用优化但又不希望重要工作被优化掉或从循环中提取出来。 - Peter Cordes
18个回答

425

CLOCKS_PER_SEC 是一个常量,它在 <time.h> 中声明。要获取在 C 应用程序中任务使用的 CPU 时间,请使用以下代码:

clock_t begin = clock();

/* here, do your time-consuming job */

clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;

注意这返回的时间是浮点类型。这可能比秒更精确(例如,您测量了4.52秒)。精度取决于架构;在现代系统上,您可以轻松地获得10毫秒或更低的精度,但在较旧的Windows机器上(从Win98时代)精度会接近60毫秒。

clock()是标准的C函数; 它可以在“任何地方”运行。还有一些特定于系统的函数,例如Unix风格系统上的getrusage()

Java的System.currentTimeMillis()不测量相同的内容。它是一个“挂钟时间”:它可以帮助您测量程序执行所需的时间,但它不告诉您使用了多少CPU时间。在多任务处理系统中(即所有系统),这两者可能大不相同。


3
clock() 返回一种内部称为“时钟”的时间尺度的时间,CLOCKS_PER_SEC 是每秒的时钟数,因此将其除以 CLOCKS_PER_SEC 可以得到以秒为单位的时间。在上面的代码中,该值是一个 double 值,因此您可以自由缩放它。 - Thomas Pornin
37
重要警告:clock() 返回操作系统运行进程所花费的时间,而非实际经过的时间。然而,这对于计时代码块来说是可以的,但不能用来测量现实世界中经过的时间。请注意区分使用场景。 - user3703887
4
他说他想要测量一个多线程程序。我不确定clock()函数是否适用于此,因为它会将所有线程的运行时间加总,所以结果看起来像是代码被顺序执行了一样。对于这种情况,我使用omp_get_wtime()函数,但当然需要确保系统没有忙于其他进程。 - Youda008
2
虽然这个主题一年前更相关,但我应该提到一些事情:CLOCKS_PER_SEC是一个值为1000000long int,当未被除时,它给出微秒级别的时间,而不是CPU时钟周期。因此,它不需要考虑动态频率,因为这里的时钟是以微秒为单位的(也许对于1 MHz的CPU来说是时钟周期?)。我编写了一个简短的C程序打印该值,在我的i7-2640M笔记本电脑上,动态频率允许800 MHz到2.8 GHz,甚至使用Turbo Boost可以高达3.5 GHz,而该值为1000000。 - DDPWNAGE
2
@Leos313:CLOCKS_PER_SEC是用于由定时器中断递增的软件时钟,或者是对其进一步处理的结果。例如,如DDPWNAGE所评论的那样,clock()通常只是微秒级别的。它与现代系统上实际CPU频率或核心时钟周期完全无关。为了解决这个问题,可以使用Linux perf stat ./a.out,默认情况下计算cycles硬件事件,例如最近的Intel CPU上的cpu_clk_unhalted.thread。与“参考时钟”的rdtsc不同,后者实际上具有固定的频率,并且即使CPU进入睡眠状态也不会停止。 - Peter Cordes
显示剩余7条评论

137
如果您正在使用Unix shell运行程序,可以使用time命令来计时。
做中。
$ time ./a.out
假设a.out作为可执行文件,则会显示程序运行所需的时间

4
仅适用于简单的程序,因为它会占用整个程序的时间,包括输入、输出等。 - phuclv
2
如果你正在使用Linux,并且已经将你的微基准测试简化为一个启动开销可以忽略不计的程序,例如运行你的热循环几秒钟的静态可执行文件,那么你可以使用perf stat ./a.out来获取缓存失效、分支预测错误和IPC的硬件性能计数器。 - Peter Cordes
1
请参阅性能评估的惯用方式?以了解有关微基准测试陷阱的更多信息,例如您需要为CPU和页面故障进行预热,或者您需要足够长的重复循环来摊销启动成本。并且您需要启用优化,但不要让您的真实工作被优化掉或将其部分提升/下沉出重复循环。 - Peter Cordes

106

在普通的C语言中:

#include <time.h>
#include <stdio.h>

int main()
{
    clock_t tic = clock();

    my_expensive_function_which_can_spawn_threads();

    clock_t toc = clock();

    printf("Elapsed: %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC);

    return 0;
}

46
我看到了最好的变量名称。tic = “时钟中的时间”,toc = “时钟外的时间”。而tic-toc = “滴答声”。从现在开始,这就是我给时间获取的标签。 - Logan Schelly
8
请注意,“tic”和“toc”是MATLAB中标准的秒表计时器函数名称,用法完全相同。因此,我不确定是否应该归功于其独创性,但这增加了它们被认可和理解的可能性。 - Cody Gray
3
@CodyGray 哦,我不知道这一点。好像十多年前我在某处看到过这些变量名 :) 我在2022年仍然使用 tictoc ,所以下次我进行代码评审时,我可以解释这个惯用法的来历,让同事们明白我用的是什么。 - Alexandre C.
1
@Leos313:正如我在另一个答案下回复的重复评论中所述,clock()函数与现代系统上的核心时钟周期无关;这对于相同的二进制文件在其他系统上工作是必要的;它们都必须使用相同的CLOCKS_PER_SEC进行编译,以匹配libc clock()在不同机器上返回的值。 - Peter Cordes
1
@Leos313:你是指像这个已有的问题吗?C语言中实际CPU频率和clock_t之间的关系是什么? - Peter Cordes
显示剩余2条评论

77
您需要的是以下功能:

#include <sys/time.h>

struct timeval  tv1, tv2;
gettimeofday(&tv1, NULL);
/* stuff to do! */
gettimeofday(&tv2, NULL);

printf ("Total time = %f seconds\n",
         (double) (tv2.tv_usec - tv1.tv_usec) / 1000000 +
         (double) (tv2.tv_sec - tv1.tv_sec));

请注意,这是以微秒为单位计量的,而不仅仅是以秒为单位。


2
MinGW编译器基于GCC,因此它可以正常工作。但是如果您使用Visual C编译器,则会出现错误。 - user2550754
11
是的,它可以在 Windows 上运行,只要使用支持 gettimeofday 调用的 C 库即可。实际上编译器并不重要,你只需要将其与一个良好的 libc 库链接即可。在 mingw 的情况下,默认的 Windows 库并不是一个良好的选择。 - Wes Hardaker
1
这在我的Windows XP上使用cygwin gcc和Linux Ubuntu上运行良好。这正是我想要的。 - Love and peace - Joe Codeswell
3
gettimeofday已经过时,不建议在新代码中使用。它的POSIX手册推荐使用clock_gettime,该函数允许您请求不受系统时钟更改影响的CLOCK_MONOTONIC,因此它更适合作为间隔时间。(请参见JohnSll的答案)。例如,在现代Linux系统上,gettimeofday基本上是一个将纳秒转换为微秒的clock_gettime包装器。 - Peter Cordes

14

这里的所有答案都存在缺陷,如果你的系统管理员更改了系统时间或者你所在的时区有不同的冬令时和夏令时。因此...

在Linux上使用:clock_gettime(CLOCK_MONOTONIC_RAW, &time_variable); 它不受系统管理员更改时间或者你所在国家冬令时与夏令时不同等影响。

#include <stdio.h>
#include <time.h>

#include <unistd.h> /* for sleep() */

int main() {
    struct timespec begin, end;
    clock_gettime(CLOCK_MONOTONIC_RAW, &begin);

    sleep(1);      // waste some time

    clock_gettime(CLOCK_MONOTONIC_RAW, &end);
    
    printf ("Total time = %f seconds\n",
            (end.tv_nsec - begin.tv_nsec) / 1000000000.0 +
            (end.tv_sec  - begin.tv_sec));

}

man clock_gettime 的说明:

CLOCK_MONOTONIC

Clock that cannot be set and represents monotonic time since some unspecified starting point. This clock is not affected by discontinuous jumps in the system time (e.g., if the system administrator manually changes the clock), but is affected by the incremental adjustments performed by adjtime(3) and NTP.


你能解释一下你用来计算秒数的公式吗?不太清楚其中的运算。 - Colin Keenan
1
这个 (end.tv_nsec - begin.tv_nsec) / 1000000000.0 不会一直得到 0 吗? - alk
@alk:不,除以double字面量会在除法之前触发int或long转换为double。当然,您可以坚持使用整数并打印tv_sec部分,然后使用零的小数部分,例如%ld.%09ld,但是将其转换为double很容易,并且通常53位精度对于基准时间来说已经足够了。 - Peter Cordes
3
哎呀,减去纳秒部分可能需要进位到秒部分,因此使用双精度并让其为负数可以避免这个问题。要使用纯整数格式字符串,您需要一个 timespec_subtract,就像 glibc 手册中建议的 timeval_subtract 一样:https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html - Peter Cordes

13

大多数简单程序的计算时间以毫秒为单位。因此,我想你会发现这很有用。

#include <time.h>
#include <stdio.h>

int main(){
    clock_t start = clock();
    // Execuatable code
    clock_t stop = clock();
    double elapsed = (double)(stop - start) * 1000.0 / CLOCKS_PER_SEC;
    printf("Time elapsed in ms: %f", elapsed);
}

如果您想计算整个程序的运行时间,并且您使用的是 Unix 系统,请使用 time 命令运行您的程序,命令如下:time ./a.out


在Windows中,至少因子是100,但不是1000,并且它不是精确的。 - boctulus
6
这个回答并没有比两年前Alexandre C回答多提供任何信息。 - Jonathan Leffler
6
@boctulus说:"1秒始终等于1000毫秒,即使在Windows操作系统上也是如此。" - alk

12

Thomas Pornin的答案作为宏:

#define TICK(X) clock_t X = clock()
#define TOCK(X) printf("time %s: %g sec.\n", (#X), (double)(clock() - (X)) / CLOCKS_PER_SEC)

像这样使用:

TICK(TIME_A);
functionA();
TOCK(TIME_A);

TICK(TIME_B);
functionB();
TOCK(TIME_B);

输出:

time TIME_A: 0.001652 sec.
time TIME_B: 0.004028 sec.

11

许多答案都建议使用time.h中的clock()CLOCKS_PER_SEC。但这可能是一个坏主意,因为我的/bits/time.h文件说:

/* ISO/IEC 9899:1990 7.12.1: <time.h>
The macro `CLOCKS_PER_SEC' is the number per second of the value
returned by the `clock' function. */
/* CAE XSH, Issue 4, Version 2: <time.h>
The value of CLOCKS_PER_SEC is required to be 1 million on all
XSI-conformant systems. */
#  define CLOCKS_PER_SEC  1000000l

#  if !defined __STRICT_ANSI__ && !defined __USE_XOPEN2K
/* Even though CLOCKS_PER_SEC has such a strange value CLK_TCK
presents the real value for clock ticks per second for the system.  */
#   include <bits/types.h>
extern long int __sysconf (int);
#   define CLK_TCK ((__clock_t) __sysconf (2))  /* 2 is _SC_CLK_TCK */
#  endif

因此,CLOCKS_PER_SEC 可能被定义为1000000,具体取决于编译时使用的选项,因此它似乎不是一个好的解决方案。


1
谢谢提供信息,但是现在有更好的替代方案吗? - ozanmuyes
5
这不是一个实际的问题:是的,Posix系统总是具有CLOCK_PER_SEC == 1000000,但同时,它们所有的clock()实现都使用1微秒的精度;顺便说一下,这具有减少共享问题的好属性。如果您想测量可能非常快的事件,比如低于1毫秒,那么您应该首先担心clock()函数的准确性(或分辨率),在Posix中必然比1微秒更粗糙,但通常也要粗得多;通常的解决方案是运行测试多次;虽然所问的问题似乎不需要这样做。 - AntoineL
为什么这不是一个好的解决方案呢?如果你从clock()函数中获取一些值,然后将该值除以CLOCK_PER_SEC,你就可以保证得到CPU所花费的时间(秒)。测量实际时钟速度的责任在于clock()函数,而不是你自己。 - Zaffy
没有编译成二进制的常数值能够计算实际的核心时钟周期并且可移植到其他系统。即使是在单一系统中计算周期,也不行,因为大约 20 年以来(在撰写本文时已有 1 年),CPU 就已经可以改变时钟频率以节省电源。这只是用于测量 CPU 时间的便捷的滴答时间间隔。如果这不是你想要的(例如挂钟时间),那就使用 clock_gettime 等其他工具。如果你想要核心时钟周期,请使用 perf stat 进行微基准测试。 - Peter Cordes

5
    #include<time.h>
    #include<stdio.h>
    int main(){
      clock_t begin=clock();

      int i;
      for(i=0;i<100000;i++){
        printf("%d",i);
      }
      clock_t end=clock();

      printf("Time taken:%lf",(double)(end-begin)/CLOCKS_PER_SEC);
    }

这个程序会像魔法一样运行。


1
这不会计算多核处理器上的执行时间(墙钟时间)。 - Björn Lindqvist

4

ANSI C只规定了秒级精度的时间函数。然而,如果您在POSIX环境中运行,则可以使用gettimeofday()函数,提供自UNIX纪元以来已经过去的微秒时间分辨率。

另外,我不建议使用clock()函数,因为在许多系统(如果不是全部)上都实现得很糟糕,而且不准确。除此之外,它仅指的是程序在CPU上运行的时间长度,而并不是程序的整个生命周期,根据您的问题,我认为您想要测量的是程序的整个生命周期。


ISO C标准(假设这就是ANSI C的意思)故意不指定时间函数的精度。 然后在POSIX实现或Windows上,挂钟(参见Thomas的答案)函数的精度为秒。 但是clock()的精度通常更高,在Posix中始终为1微秒(与准确性无关)。 - AntoineL

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