我们如何衡量函数执行所需的时间?

时间:2020-03-05 18:55:01  来源:igfitidea点击:

我们如何测量函数执行所需的时间?

这是一个相对较短的函数,执行时间可能在毫秒范围内。

这个特定问题与使用C或者C ++编程的嵌入式系统有关。

解决方案

回答

start_time = timer
function()
exec_time = timer - start_time

回答

以大量调用循环调用它,然后除以调用次数即可获得平均时间。

所以:

// begin timing
for (int i = 0; i < 10000; i++) {
    invokeFunction();
}
// end time
// divide by 10000 to get actual time.

回答

在嵌入式系统上执行此操作的最佳方法是在进入功能时设置外部硬件引脚,并在退出功能时将其清除。最好通过一点汇编指令来完成此操作,这样我们就不会将结果歪曲得太多。

编辑:好处之一是我们可以在实际的应用程序中执行此操作,而无需任何特殊的测试代码。像这样的外部调试引脚是(应该是!)每个嵌入式系统的标准做法。

回答

如果我们正在寻找亚毫秒级分辨率,请尝试以下计时方法之一。它们都会在至少几十或者几百微秒的时间内为我们提供分辨率:

如果是嵌入式Linux,请查看Linux计时器:

http://linux.die.net/man/3/clock_gettime

嵌入式Java,看一下nanoTime(),尽管我不确定这在嵌入式版本中:

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html#nanoTime()

如果要获取硬件计数器,请尝试使用PAPI:

http://icl.cs.utk.edu/papi/

否则,我们可以随时转到汇编器。如果我们需要有关此方面的帮助,则可以查看体系结构的PAPI源代码。

回答

在OS X终端(也可能是Unix)中,使用"时间":

time python function.py

回答

如果代码是.Net,请使用秒表类(.net 2.0+),而不是DateTime.Now。 DateTime.Now的更新不够准确,会给我们疯狂的结果

回答

如果我们使用的是Linux,则可以通过在命令行中键入时间来计时程序的运行时间:

time [funtion_name]

如果仅在main()中运行函数(假设使用C ++),则应用程序的其余时间应该可以忽略不计。

回答

有三种潜在的解决方案:

硬件解决方案:

使用处理器上的自由输出引脚,并将示波器或者逻辑分析仪连接至该引脚。在调用要测量的功能之前,将引脚初始化为低电平状态,将引脚置为高电平,然后在从功能返回之后,将其置为无效。

*io_pin = 1;
    myfunc();
    *io_pin = 0;

Bookworm解决方案:

如果函数很小,并且我们可以管理反汇编的代码,则可以打开处理器体系结构数据手册并计算处理器执行每条指令所需的周期。这将为我们提供所需的循环数。
时间=周期*处理器时钟速率/每个指令的时钟滴答

对于较小的功能或者用汇编器编写的代码(例如,对于PIC微控制器),这更容易实现。

时间戳计数器解决方案:

一些处理器具有时间戳计数器,该计数器以快速速率(每隔几个处理器时钟周期)递增。只需阅读函数前后的时间戳。
这将为我们提供经过的时间,但请注意,我们可能必须处理计数器翻转。

回答

Windows XP / NT Embedded或者Windows CE / Mobile

我们可以使用QueryPerformanceCounter()来获取函数前后的非常快速计数器的值。然后,将这些64位值相减并得到增量"滴答"。使用QueryPerformanceCounterFrequency(),可以将"增量滴答"转换为实际时间单位。我们可以参考有关那些WIN32调用的MSDN文档。

其他嵌入式系统

在没有操作系统或者仅具有基本操作系统的情况下,我们将必须:

  • 对内部CPU定时器之一进行编程以自由运行和计数。
  • 将其配置为在定时器溢出时产生中断,并在此中断例程中增加一个"进位"变量(这样我们实际上可以测量的时间长于所选定时器的分辨率)。
  • 在我们执行功能之前,请保存"进位"值和保存我们配置的计数计时器的运行记号的CPU寄存器的值。
  • 同样的功能
  • 减去它们以获得增量计数器刻度。
  • 从那里开始,只要知道设置外部时钟和在设置计时器时配置的解乘法,对CPU /硬件的滴答声就意味着多长时间。我们可以将"刻度线长度"乘以我们刚刚获得的"增量刻度"。

非常重要不要忘记在获得那些定时器值(使进位和寄存器值无效)之前先禁用并恢复中断,否则我们可能会保存不正确的值。

笔记

  • 这非常快,因为只有几条汇编指令可以禁止中断,保存两个整数值并重新启用中断。实际的减法和转换成实时单位发生在时间测量范围之外,即在功能之后。
  • 我们可能希望将代码放入函数中以重新使用该代码,但是由于函数调用以及将所有寄存器与参数一起压入堆栈,然后再次弹出它们,可能会使事情变慢。在嵌入式系统中,这可能很重要。最好在C语言中改用MACROS或者编写自己的汇编例程,以仅保存/恢复相关的寄存器。

回答

取决于嵌入式平台和所需的时间类型。对于嵌入式Linux,有几种方法可以完成。如果要测量函数使用的CPU时间,可以执行以下操作:

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

#define SEC_TO_NSEC(s) ((s) * 1000 * 1000 * 1000)

int work_function(int c) {
    // do some work here
    int i, j;
    int foo = 0;
    for (i = 0; i < 1000; i++) {
        for (j = 0; j < 1000; j++) {
            for ^= i + j;
        }
    }
}

int main(int argc, char *argv[]) {
    struct timespec pre;
    struct timespec post;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &pre);
    work_function(0);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &post);

    printf("time %d\n",
        (SEC_TO_NSEC(post.tv_sec) + post.tv_nsec) -
        (SEC_TO_NSEC(pre.tv_sec) + pre.tv_nsec));
    return 0;
}

我们需要将其与实时库链接,只需使用以下代码来编译代码:

gcc -o test test.c -lrt

我们可能还想阅读clock_gettime上的手册页,在基于SMP的系统上运行此代码存在一些问题,这些问题可能会使测试无效。我们可以使用诸如" sched_setaffinity()"之类的内容或者命令行" cpuset"之类的代码来仅在一个内核上强制执行。

如果我们要测量用户和系统时间,则可以使用times(NULL)返回类似jiffies的东西。或者,我们可以将Clock_gettime()的参数从CLOCK_THREAD_CPUTIME_ID更改为CLOCK_MONOTONIC。

对于其他平台,则由我们自己决定。

德鲁

回答

我总是实现一个中断驱动的股票行情例程。然后,这将更新一个计数器,该计数器对自启动以来的毫秒数进行计数。然后使用GetTickCount()函数访问此计数器。

例子:

#define TICK_INTERVAL 1    // milliseconds between ticker interrupts
static unsigned long tickCounter;

interrupt ticker (void)  
{
    tickCounter += TICK_INTERVAL;
    ...
}

unsigned in GetTickCount(void)
{
    return tickCounter;
}

在代码中,我们将对代码进行计时,如下所示:

int function(void)
{
    unsigned long time = GetTickCount();

    do something ...

    printf("Time is %ld", GetTickCount() - ticks);
}

回答

我重复了很多次(数百万次)函数调用,但是还采用了以下方法来减轻循环开销:

start = getTicks();

repeat n times {
    myFunction();
    myFunction();
}

lap = getTicks();

repeat n times {
    myFunction();
}

finish = getTicks();

// overhead + function + function
elapsed1 = lap - start;

// overhead + function
elapsed2 = finish - lap;

// overhead + function + function - overhead - function = function
ntimes = elapsed1 - elapsed2;

once = ntimes / n; // Average time it took for one function call, sans loop overhead

我们可以在第一个循环中调用一次,而在第二个循环中根本不调用它(即空循环),而不是在第一个循环中调用function()两次,在第二个循环中调用一次由编译器优化,给我们负面的计时结果:)