使用 timex() 与时间戳测量 VxWorks 执行时间
在 VxWorks 上优化实时应用程序时,准确测量执行时间至关重要。timex() 工具提供了一种便捷的方式,可从 VxWorks shell 或应用程序代码中对函数进行基准测试;而 sysTimestamp() 等 API 则提供显著更精细的计时分辨率,适用于详细性能分析。
合适的计时机制取决于工作负载和所需精度。基于系统时钟节拍的 API 足以进行粗粒度测量,而硬件时间戳计数器更适合短代码路径、中断服务例程以及性能敏感的内核代码。
🔧 timex() 如何测量执行时间
#
VxWorks 的 timex() 函数通过 usrLib 实用程序库提供。它执行目标函数、测量经过的系统时钟节拍、估算测量开销,并报告最终的执行时间。
测量工作流程 #
基本过程如下:
- 捕获当前系统时钟节拍计数。
- 执行目标函数。
- 捕获结束时的节拍计数。
- 单独测量计时开销。
- 从测量的时间间隔中减去估算的开销。
- 使用系统时钟频率将所得节拍数转换为时间值。
- 将测量结果打印到 VxWorks 控制台。
基本转换公式为:
$$ \text{执行时间(秒)} #
\frac{\Delta\text{ticks}}{\text{sysClkRateGet()}} $$
例如,在 1000 Hz 系统时钟下,一个节拍约代表 1 ms;在 100 Hz 时钟下,标称节拍间隔约 10 ms。
由于测量本质上与系统时钟节拍绑定,timex() 更适合相对粗粒度的基准测试,而非纳秒级性能分析。
🧩 timex() API 与调用约定
#
该 API 接受一个函数指针,后跟最多八个整数参数:
void timex(
FUNCPTR function_name,
int arg1,
int arg2,
int arg3,
int arg4,
int arg5,
int arg6,
int arg7,
int arg8
);
因此,目标函数可通过 timex() 接口接收最多八个参数。在 32 位 VxWorks 目标上,整数参数也可在适当情况下用于传递指针大小的值。
未使用的参数应初始化为 0。
例如:
timex((FUNCPTR)myWorkload,
1000000,
42,
0,
0,
0,
0,
0,
0);
这将调用:
myWorkload(1000000, 42);
目标函数在调用任务的上下文中执行。当从 VxWorks shell 调用时,这通常是 shell 任务,而非专用的基准测试任务。
📊 比较 VxWorks 计时机制 #
| 计时方法 | 分辨率 | 最佳用例 | 优点 | 局限性 |
| :------------------ | :---------------------------------- | :------------------------------------- | :-------------------------- | :---------------------------------------- |
| `timex()` | 系统时钟节拍级 | 快速函数基准测试 | 简单且 shell 友好 | 受系统时钟节拍分辨率限制 |
| `tickGet()` | 系统时钟节拍级 | 长时任务计时 | 轻量级内核 API | 对短操作过于粗糙 |
| `sysTimestamp()` | 硬件计数器级 | 细粒度分析与 ISR 计时 | 高分辨率测量 | 依赖 BSP 与硬件 |
| `clock_gettime()` | POSIX 时钟,通常为纳秒级 API | 可移植的应用层分析 | 标准 POSIX 接口 | 需要相应的 POSIX 定时器支持 |
sysTimestamp() 报告的分辨率取决于底层 BSP 和硬件定时器。其 API 暴露的是硬件计数器,而非相对粗糙的调度器时钟节拍。
⚡ 使用 sysTimestamp() 进行高分辨率分析
#
对于短执行路径,sysTimestamp() 通常比 timex() 更有用,因为它读取高分辨率时间戳计数器。
典型的测量序列如下:
startTs = sysTimestamp();
myWorkload(1000000, 42);
endTs = sysTimestamp();
然后根据时间戳频率计算经过的时间间隔:
$$ \text{执行时间} #
\frac{\text{结束时间戳} - \text{开始时间戳}} {\text{sysTimestampFreq()}} $$
这种方法避免依赖系统调度器时钟节拍,并能揭示 tickGet() 或 timex() 无法察觉的性能差异。
然而,时间戳计数器依赖硬件和 BSP。应用程序应在将其作为分析原语使用前,验证时间戳支持是否可用。
🧪 完整的 C 计时示例 #
以下示例比较了 timex() 的便捷性与更高分辨率的 sysTimestamp() 测量:
#include <vxWorks.h>
#include <stdio.h>
#include <usrLib.h>
#include <sysLib.h>
#include <timestampLib.h>
/* 待基准测试的工作负载 */
void myWorkload(int loopCount, int value)
{
volatile int i;
for (i = 0; i < loopCount; i++) {
value = (value * 3) ^ i;
}
}
/* 演示多种计时机制 */
void testTiming(void)
{
UINT32 startTs;
UINT32 endTs;
UINT32 freq;
double timeInSeconds;
printf("=== Method 1: timex() ===\n");
/*
* 执行:
* myWorkload(1000000, 42)
*/
timex((FUNCPTR)myWorkload,
1000000,
42,
0,
0,
0,
0,
0,
0);
printf("\n=== Method 2: sysTimestamp() ===\n");
if (sysTimestampConnect(NULL, 0) == OK &&
sysTimestampEnable() == OK) {
freq = sysTimestampFreq();
startTs = sysTimestamp();
myWorkload(1000000, 42);
endTs = sysTimestamp();
timeInSeconds =
(double)(endTs - startTs) / (double)freq;
printf("Elapsed Time: %.6f seconds "
"(%u ticks @ %u Hz)\n",
timeInSeconds,
(endTs - startTs),
freq);
}
else {
printf("Hardware timestamp timer is not "
"supported by this BSP.\n");
}
}
这两种测量服务于不同目的。timex() 提供可直接从 shell 调用的便捷基线,而 sysTimestamp() 允许使用平台更高分辨率的时间戳设施测量相同的工作负载。
🧠 实用性能分析注意事项 #
孤立地对函数进行计时,并不一定代表其在生产环境中的执行时间。VxWorks 任务调度、缓存状态、中断、分支预测、内存争用以及编译器优化都可能影响结果。
对短工作负载进行重复测量 #
如果目标函数仅在几微秒内完成,测量开销相对于工作负载本身可能变得显著。多次重复操作并测量总时间间隔,可产生更稳定的结果。
控制编译器优化 #
如果编译器移除或转换了工作负载,基准测试可能被扭曲。示例中使用了 volatile 循环变量,使循环的可观察行为更难被消除,但当编译器优化很重要时,生产基准测试仍应在生成的代码级别进行检查。
考虑缓存状态 #
第一次调用可能包含后续调用可避免的指令缓存和数据缓存未命中。对于有意义的稳态测量,应分别进行冷缓存和热缓存测试,而非将其视为等价。
考虑中断与调度干扰 #
VxWorks 是实时操作系统,但正常执行仍可能被 ISR 和更高优先级任务打断。对于延迟敏感的基准测试,应测量多次迭代并检查分布,而非仅依赖单次结果。
🎯 选择合适的计时 API #
当主要需求是快速便捷的函数基准测试(尤其是从 VxWorks shell)时,使用 timex()。
当测量相对长时间运行的操作且系统时钟节拍精度足够时,使用 tickGet()。
当测量短执行路径、驱动例程、ISR 相关代码或微架构性能且 BSP 提供合适的硬件时间戳源时,使用 sysTimestamp()。
当POSIX 可移植性和应用层计时比直接访问平台特定时间戳硬件更重要时,使用 clock_gettime()。
对于严肃的性能分析,将粗粒度系统级测量与高分辨率时间戳分析相结合,可提供更完整的 VxWorks 应用程序行为视图。