题
如何测量函数执行所需的时间?
这是一个相对较短的函数,执行时间可能在毫秒范围内。
这个特定问题涉及用 C 或 C++ 编程的嵌入式系统。
解决方案
在嵌入式系统上做到这一点的最佳方法是在进入该功能时设置外部硬件引脚,并在离开该功能时清除它。最好使用一些汇编指令来完成此操作,这样您的结果就不会偏差太大。
编辑:好处之一是您可以在实际应用程序中执行此操作,并且不需要任何特殊的测试代码。像这样的外部调试引脚是(应该是!)每个嵌入式系统的标准做法。
其他提示
存在三种可能的解决方案:
硬件方案:
使用处理器上的空闲输出引脚,并将示波器或逻辑分析仪连接到该引脚。将引脚初始化为低电平状态,在调用要测量的函数之前,将引脚断言为高电平状态,并在从函数返回后立即取消断言引脚。
*io_pin = 1;
myfunc();
*io_pin = 0;
书虫解决方案:
如果函数相当小,并且您可以管理反汇编代码,则可以打开处理器架构数据手册并计算处理器执行每条指令所需的周期。这将为您提供所需的周期数。
时间 = # 个周期 * 处理器时钟速率 / 每条指令的时钟周期
对于较小的函数或用汇编程序编写的代码(例如 PIC 微控制器),这更容易做到
时间戳计数器解决方案:
某些处理器具有时间戳计数器,该计数器会快速递增(每隔几个处理器时钟滴答声)。只需读取函数前后的时间戳即可。这将为您提供经过的时间,但请注意您可能需要处理计数器翻转。
在具有大量调用的循环中调用它,然后除以调用次数以获得平均时间。
所以:
// begin timing
for (int i = 0; i < 10000; i++) {
invokeFunction();
}
// end time
// divide by 10000 to get actual time.
如果您使用的是 Linux,您可以通过在命令行中键入以下内容来计时程序的运行时间:
time [funtion_name]
如果您只运行 main() 中的函数(假设是 C++),则应用程序的其余时间应该可以忽略不计。
我多次重复该函数调用(数百万次),但也采用以下方法来减少循环开销:
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() ,而根本不调用它,而不是在第一个循环中调用 function() 两次,在第二个循环中调用一次。空循环)在第二个中,但是空循环可以被编译器优化掉,给你负面的计时结果:)
start_time = timer
function()
exec_time = timer - start_time
Windows XP/NT 嵌入式或 Windows CE/移动
您可以使用 QueryPerformanceCounter() 在函数之前和之后获取非常快的计数器的值。然后减去这些 64 位值并得到增量“刻度”。使用 QueryPerformanceCounterFrequency() 您可以将“增量刻度”转换为实际时间单位。您可以参考有关这些 WIN32 调用的 MSDN 文档。
其他嵌入式系统
如果没有操作系统或只有基本操作系统,您将必须:
- 对内部 CPU 定时器之一进行编程以自由运行和计数。
- 将其配置为在定时器溢出时生成中断,并在此中断例程中增加一个“进位”变量(这样您实际上可以测量比所选定时器的分辨率更长的时间)。
- 在您的函数之前,您保存“进位”值和保存您配置的计数定时器的运行滴答的CPU寄存器的值。
- 在你的函数之后也一样
- 减去它们以获得增量计数器刻度。
- 从那里开始,只需知道在给定外部时钟和设置定时器时配置的去乘法的情况下,一个滴答声对您的 CPU/硬件意味着多长时间即可。您将“刻度长度”乘以刚刚获得的“增量刻度”。
很重要 不要忘记在获取这些定时器值(bot 进位和寄存器值)之前禁用中断并恢复中断,否则您可能会保存不正确的值。
笔记
- 这非常快,因为只需几条汇编指令即可禁用中断、保存两个整数值并重新启用中断。实际的减法和转换为实时单位发生在时间测量区域之外,即在您的函数之后。
- 您可能希望将该代码放入一个函数中以重用该代码,但由于函数调用和将所有寄存器推入堆栈以及参数,然后再次弹出它们,它可能会减慢速度。在嵌入式系统中,这可能很重要。使用 MACROS 或编写自己的汇编例程仅保存/恢复相关寄存器可能比在 C 中更好。
取决于您的嵌入式平台以及您正在寻找的计时类型。对于嵌入式 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
...但要小心环绕 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);
}
在 OS X 终端(也可能是 Unix)中,使用“time”:
time python function.py
如果代码是 .Net,请使用秒表类 (.net 2.0+) 而不是 DateTime.Now。DateTime.Now 更新不够准确,会给你带来疯狂的结果
如果您正在寻找亚毫秒级分辨率,请尝试以下计时方法之一。它们都会在至少数十或数百微秒内为您提供解决方案:
如果是嵌入式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:
否则你总是可以去汇编器。如果您需要一些帮助,您可以查看您的架构的 PAPI 源代码。