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个回答

3

需要考虑的是,测量程序执行所需时间取决于机器在特定时刻的负载情况。

了解这一点后,在C语言中获取当前时间的方法有很多种,其中比较简单的一种是:

#include <time.h>

#define CPU_TIME (getrusage(RUSAGE_SELF,&ruse), ruse.ru_utime.tv_sec + \
  ruse.ru_stime.tv_sec + 1e-6 * \
  (ruse.ru_utime.tv_usec + ruse.ru_stime.tv_usec))

int main(void) {
    time_t start, end;
    double first, second;

    // Save user and CPU start time
    time(&start);
    first = CPU_TIME;

    // Perform operations
    ...

    // Save end time
    time(&end);
    second = CPU_TIME;

    printf("cpu  : %.2f secs\n", second - first); 
    printf("user : %d secs\n", (int)(end - start));
}

希望这有所帮助。 祝好!

3

我发现通常被推荐的clock()函数在运行时有很大偏差,即使是没有任何副作用(如屏幕绘制或读取文件)的静态代码。这可能是因为CPU改变了电源消耗模式,操作系统给出了不同的优先级等。

因此,可靠地每次得到相同结果的唯一方法是在一个循环中多次运行测量的代码(几分钟),并采取预防编译器对其进行优化的措施:现代编译器可以对在循环中运行且没有副作用的代码进行预计算,并将其移出循环。例如使用每次迭代的随机输入。

当收集足够的样本到一个数组中后,对该数组进行排序,并取中间元素,称为中位数。中位数比平均值更好,因为它排除了极端偏差,例如杀毒软件占用所有CPU或操作系统执行某些更新。

以下是一个简单的实用程序,用于测量C / C ++代码的执行性能,并平均中间值附近的值:https://github.com/saniv/gauge

我自己仍在寻找更强大,更快速的测量代码的方法。可能可以尝试在没有任何操作系统的控制下以受控条件运行代码,但这将给出不切实际的结果,因为实际上操作系统会介入。

x86有这些硬件性能计数器,包括实际执行的指令数量,但是它们很难在没有操作系统帮助的情况下访问,解释起来也很困难,并且具有自己的问题(http://archive.gamedev.net/archive/reference/articles/article213.html)。但仍然可以通过它们来调查瓶颈的性质(数据访问或对该数据的实际计算)。


是的,现代x86 CPU的空闲速度比最大睿频慢得多。根据“governor”设置,达到最大时钟速度可能需要一毫秒(具有硬件P状态管理的Skylake,特别是将energy_performance_preference设置为“performance”)或许多十毫秒。https://en.wikipedia.org/wiki/Dynamic_frequency_scaling。而且,通常中等性能是一个不错的选择;高端通常会受到干扰而产生一些波动。 - Peter Cordes
通常避免工作被优化的最佳方法是使用命令行输入并返回结果。或者编写一个在main函数中独立于其他文件的函数,该函数接受一个参数并返回结果,并且不使用链接时优化。然后编译器无法将其内联到调用者中。只有当函数已经包含某种循环时才有效,否则调用/返回开销太高。 - Peter Cordes
编译器仍然可以优化循环之外的单个命令行输入,如果您使用没有任何副作用的静态代码进行处理。因此,最好为每个迭代生成随机输入。显然,rand()应该在测量代码之外调用,在第一个clock()之前,因为rand()也可能导致系统调用,采样一些硬件熵生成器(在旧系统上是鼠标移动)。只是不要忘记printf输出的每一位,否则编译器可能会决定您不需要所有输出作为整体或部分。这可以通过CRC32等方式完成。 - SmugLispWeenie
禁用链接时优化(即-fno-lto)的问题在于,实际上您希望启用所有可能的优化,并检查启用特定优化类型是否确实使代码更快。在我的情况下,-flto 在某些情况下使代码变慢(约为1.15倍),这表明在您调整发布版本构建标志时,从链接时优化或其他优化类型中排除特定文件是有意义的。此外,编译器应该具有一些自动化功能,以进行此类试错。 - SmugLispWeenie
1
我认为最好的解决方案是将基准测试代码放在共享库中,将测量代码放入可执行文件中,并使用dlopen打开该库。现在,您还可以在多个项目中重用该测量器或测试一个项目的多个部分。这就是我正在做的事情。除非LLVM有一天学会优化DLL链接或执行其他JIT操作,否则这应该可以工作,尽管这是一个可预测性的噩梦,而且根本无法测量。 - SmugLispWeenie
显示剩余3条评论

2

有些人可能会发现不同类型的输入很有用:我在一门关于使用NVidia CUDA进行GPGPU编程的大学课程中学到了这种测量时间的方法(课程描述)。它结合了早期帖子中提到的方法,我只是发布它是因为要求使它更加可信:

最初的回答

unsigned long int elapsed;
struct timeval t_start, t_end, t_diff;
gettimeofday(&t_start, NULL);

// perform computations ...

gettimeofday(&t_end, NULL);
timeval_subtract(&t_diff, &t_end, &t_start);
elapsed = (t_diff.tv_sec*1e6 + t_diff.tv_usec);
printf("GPU version runs in: %lu microsecs\n", elapsed);

我想你可以使用例如1.0 / 1000.0来进行乘法运算,以获得适合你需要的测量单位。最初的回答。

1
gettimeofday已经过时并不推荐使用。它的POSIX手册建议使用[clock_gettime](http://pubs.opengroup.org/onlinepubs/009696899/functions/clock_getres.html)代替,该函数可以让你请求`CLOCK_MONOTONIC`而不受系统时钟变化的影响,因此它更适合作为一个间隔计时器。例如,在现代Linux系统上,`gettimeofday`基本上是对`clock_gettime`的一个包装器,将纳秒转换为微秒。(请参见JohnSll的回答)。 - Peter Cordes
这个方法是由@Wes Hardaker添加的,主要区别在于使用timeval_subtract - alexpanter
好的,你回答中唯一有用的部分是一个你没有定义过的函数的名称,而且这个函数也不在标准库中。(只在glibc手册中有:https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html)。 - Peter Cordes

2

我的系统中所有的解决方案都无法使用。

我可以使用以下方式获取:

#include <time.h>

double difftime(time_t time1, time_t time0);

3
这将两个 time_t 值的差作为双精度数值返回。由于 time_t 值只能精确到秒,因此对于打印短时间运行程序所需的时间来说,其价值有限,但对于长时间运行的程序可能会有用。 - Jonathan Leffler
出于某种原因,将一对 clock_t 传递给 difftime 对我来说似乎可以精确到百分之一秒的精度。这是在 Linux x86 上的情况。我也无法让 stopstart 的减法运算起作用。 - interestedparty333
@ragerdl:你需要传递给 difftime() 函数 clock() / CLOCKS_PER_SEC,因为它期望的是秒数。 - alk

2

如果您的程序使用了GPU或者使用了sleep()函数,那么clock()函数得到的时间差将会比实际持续时间小。这是因为clock()函数返回的是CPU时钟滴答数。它只能用来计算CPU使用时间(CPU负载),而不能用于执行持续时间的计算。我们不应该使用clock()函数来计算持续时间。在C语言中,我们仍然应该使用gettimeofday()clock_gettime()函数来计算持续时间。


clock() 不计算 CPU 时钟周期;它以 CLOCKS_PER_SEC 为单位计算用户空间 CPU 时间,该值由 POSIX 固定为 1000000(1 M)。在可以自行变化频率的 CPU 上计算实际核心时钟周期(如 turbo / boost 时钟)需要硬件性能计数器,例如 perf stat 使用的。但是,是的,它确实计算 CPU 时间而不是挂钟时间。 - Peter Cordes

1

perf工具更准确,可用于收集和分析运行中的程序。使用perf stat来显示与正在执行的程序相关的所有信息。


0

通过使用类似函数的宏,尽可能简单

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

#define printExecTime(t) printf("Elapsed: %f seconds\n", (double)(clock()-(t)) / CLOCKS_PER_SEC)

int factorialRecursion(int n) {
    return n == 1 ? 1 : n * factorialRecursion(n-1);
}

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

    int j=1;
    for(int i=1; i <10; i++ , j*=i);

    printExecTime(t);
    
    // compare with recursion factorial
    t = clock();
    j = factorialRecursion(10);
    printExecTime(t);

    return 0;
}

通常情况下,您不希望计时 printf("factorial ... %d),但这段代码却在这样做。当然,与 clock() 的测量开销相比,计算 10! 如此之快,以至于仅计时计算并不能得到太多信息。如果编译器无法通过循环进行常量传播以仅打印常量 j 值,则需要参考 Idiomatic way of performance evaluation? 有关编译优化的必要性,但也需要确保工作不会被优化掉。 - Peter Cordes
此外,clock 度量的是此进程的用户空间 CPU 时间,而不是挂钟时间。 如果您不想计时 I/O 等待时间,这可能是您想要的。 - Peter Cordes
这个答案似乎和一个已有的回答没有什么区别,后者使用了相似的打印宏。 - Peter Cordes
谢谢Peter的评论,但答案并不是100%重复的。如果您需要在同一个函数中测量多个代码部分(例如在main函数中),那么使用TICK TOCK方法将定义多个变量可能更好。最好定义一个变量t,在每次测量之前用clock()初始化它,然后调用宏等。关于阶乘的for循环,这只是一个示例,如果您想要,可以随意替换为递归版本的阶乘函数。 - Zakaria
我下投票的主要原因是这个宏的使用示例很糟糕,其中包含了printf在定时区域内。请至少修复这个问题或者在文本中讨论你有意进行I/O基准测试的情况,如果你认为这个答案有价值的话。同意宏的细节有些不同,并且在TICK()宏内声明变量对于某些用例来说并不是很好。多个单独的未同时使用的clock_t变量基本上不会成为优化器的问题,所以这只是一个风格问题。 - Peter Cordes
好的,这是一个改进;对于clock()来说它们仍然是不及时的短时间间隔(即使在x86上使用原始rdtsc也很有挑战性),但当然真实的用例将想要计时其他事情。 - Peter Cordes

-2

冒泡排序和选择排序的执行时间比较 我有一个程序,比较冒泡排序和选择排序的执行时间。 为了找出一块代码的执行时间,需要在代码块之前和之后计算时间

 clock_t start=clock();
 …
 clock_t end=clock();
 CLOCKS_PER_SEC is constant in time.h library

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a[10000],i,j,min,temp;
   for(i=0;i<10000;i++)
   {
      a[i]=rand()%10000;
   }
   //The bubble Sort
   clock_t start,end;
   start=clock();
   for(i=0;i<10000;i++)
   {
     for(j=i+1;j<10000;j++)
     {
       if(a[i]>a[j])
       {
         int temp=a[i];
         a[i]=a[j];
         a[j]=temp;
       }
     }
   }
   end=clock();
   double extime=(double) (end-start)/CLOCKS_PER_SEC;
   printf("\n\tExecution time for the bubble sort is %f seconds\n ",extime);

   for(i=0;i<10000;i++)
   {
     a[i]=rand()%10000;
   }
   clock_t start1,end1;
   start1=clock();
   // The Selection Sort
   for(i=0;i<10000;i++)
   {
     min=i;
     for(j=i+1;j<10000;j++)
     {
       if(a[min]>a[j])
       {
         min=j;
       }
     }
     temp=a[min];
     a[min]=a[i];
     a[i]=temp;
   }
   end1=clock();
   double extime1=(double) (end1-start1)/CLOCKS_PER_SEC;
   printf("\n");
   printf("\tExecution time for the selection sort is %f seconds\n\n", extime1);
   if(extime1<extime)
     printf("\tSelection sort is faster than Bubble sort by %f seconds\n\n", extime - extime1);
   else if(extime1>extime)
     printf("\tBubble sort is faster than Selection sort by %f seconds\n\n", extime1 - extime);
   else
     printf("\tBoth algorithms have the same execution time\n\n");
}

7
adimoh答案相比,这并没有添加任何新内容,除了使用一些实际代码填充“可执行代码”块(或两个块)。而那个答案并没有添加任何不在两年前Alexandre C答案中的内容。 - Jonathan Leffler

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