如何测量 ARM Cortex-A8 处理器中的程序执行时间?
Posted
技术标签:
【中文标题】如何测量 ARM Cortex-A8 处理器中的程序执行时间?【英文标题】:How to measure program execution time in ARM Cortex-A8 processor? 【发布时间】:2011-03-15 21:53:30 【问题描述】:我正在使用基于 ARM Cortex-A8 的处理器,称为 i.MX515。有 linux Ubuntu 9.10 发行版。我正在运行一个用 C 编写的非常大的应用程序,我正在使用 gettimeofday();
函数来测量我的应用程序所花费的时间。
main()
gettimeofday(start);
....
....
....
gettimeofday(end);
这种方法足以查看我的应用程序的哪些块花费了多少时间。但是,现在,我正在尝试非常彻底地优化我的代码,使用 gettimeofday() 计算时间的方法,我看到连续运行之间有很多波动(在我的优化之前和之后运行),所以我不能来确定实际的执行时间,从而影响我的改进。
谁能建议我该怎么做?
如果通过访问循环计数器(ARM 网站上针对 Cortex-M3 建议的想法),任何人都可以向我指出一些代码,这些代码为我提供了访问计时器所必须遵循的步骤 在 Cortex-A8 上注册?
如果此方法不是很准确,请提出一些替代方案。
谢谢
跟进
跟进1:在Code Sorcery上写了以下程序,生成了可执行文件,当我尝试在板上运行时,我得到-非法指令消息:(
static inline unsigned int get_cyclecount (void)
unsigned int value;
// Read CCNT Register
asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));
return value;
static inline void init_perfcounters (int32_t do_reset, int32_t enable_divider)
// in general enable all counters (including cycle counter)
int32_t value = 1;
// peform reset:
if (do_reset)
value |= 2; // reset all counters to zero.
value |= 4; // reset cycle counter to zero.
if (enable_divider)
value |= 8; // enable "by 64" divider for CCNT.
value |= 16;
// program the performance-counter control-register:
asm volatile ("MCR p15, 0, %0, c9, c12, 0\t\n" :: "r"(value));
// enable all counters:
asm volatile ("MCR p15, 0, %0, c9, c12, 1\t\n" :: "r"(0x8000000f));
// clear overflows:
asm volatile ("MCR p15, 0, %0, c9, c12, 3\t\n" :: "r"(0x8000000f));
int main()
/* enable user-mode access to the performance counter*/
asm ("MCR p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));
/* disable counter overflow interrupts (just in case)*/
asm ("MCR p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));
init_perfcounters (1, 0);
// measure the counting overhead:
unsigned int overhead = get_cyclecount();
overhead = get_cyclecount() - overhead;
unsigned int t = get_cyclecount();
// do some stuff here..
printf("\nHello World!!");
t = get_cyclecount() - t;
printf ("function took exactly %d cycles (including function call) ", t - overhead);
get_cyclecount();
return 0;
跟进 2:我已经写信给飞思卡尔寻求支持,他们给我发回了以下回复和一个程序 (我对此不太了解)
以下是我们现在可以为您提供的帮助: 我正在向您发送一个代码示例,该示例使用 UART 发送流,从您的代码来看,您似乎没有正确初始化 MPU。
(hash)include <stdio.h>
(hash)include <stdlib.h>
(hash)define BIT13 0x02000
(hash)define R32 volatile unsigned long *
(hash)define R16 volatile unsigned short *
(hash)define R8 volatile unsigned char *
(hash)define reg32_UART1_USR1 (*(R32)(0x73FBC094))
(hash)define reg32_UART1_UTXD (*(R32)(0x73FBC040))
(hash)define reg16_WMCR (*(R16)(0x73F98008))
(hash)define reg16_WSR (*(R16)(0x73F98002))
(hash)define AIPS_TZ1_BASE_ADDR 0x70000000
(hash)define IOMUXC_BASE_ADDR AIPS_TZ1_BASE_ADDR+0x03FA8000
typedef unsigned long U32;
typedef unsigned short U16;
typedef unsigned char U8;
void serv_WDOG()
reg16_WSR = 0x5555;
reg16_WSR = 0xAAAA;
void outbyte(char ch)
while( !(reg32_UART1_USR1 & BIT13) );
reg32_UART1_UTXD = ch ;
void _init()
void pause(int time)
int i;
for ( i=0 ; i < time ; i++);
void led()
//Write to Data register [DR]
*(R32)(0x73F88000) = 0x00000040; // 1 --> GPIO 2_6
pause(500000);
*(R32)(0x73F88000) = 0x00000000; // 0 --> GPIO 2_6
pause(500000);
void init_port_for_led()
//GPIO 2_6 [73F8_8000] EIM_D22 (AC11) DIAG_LED_GPIO
//ALT1 mode
//IOMUXC_SW_MUX_CTL_PAD_EIM_D22 [+0x0074]
//MUX_MODE [2:0] = 001: Select mux mode: ALT1 mux port: GPIO[6] of instance: gpio2.
// IOMUXC control for GPIO2_6
*(R32)(IOMUXC_BASE_ADDR + 0x74) = 0x00000001;
//Write to DIR register [DIR]
*(R32)(0x73F88004) = 0x00000040; // 1 : GPIO 2_6 - output
*(R32)(0x83FDA090) = 0x00003001;
*(R32)(0x83FDA090) = 0x00000007;
int main ()
int k = 0x12345678 ;
reg16_WMCR = 0 ; // disable watchdog
init_port_for_led() ;
while(1)
printf("Hello word %x\n\r", k ) ;
serv_WDOG() ;
led() ;
return(1) ;
【问题讨论】:
您总是可以在大量运行中获取平均执行时间。 ARM11 和 Cortex-A/R 的性能监控单元示例代码:infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/… 【参考方案1】:访问性能计数器并不困难,但您必须从内核模式启用它们。默认情况下,计数器被禁用。
简而言之,您必须在内核中执行以下两行代码。无论是作为可加载模块,还是在 board-init 中的某处添加两行都可以:
/* enable user-mode access to the performance counter*/
asm ("MCR p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));
/* disable counter overflow interrupts (just in case)*/
asm ("MCR p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));
完成此操作后,循环计数器将开始为每个循环递增。寄存器溢出不会引起注意,不会引起任何问题(除非它们可能会弄乱您的测量)。
现在你想从用户模式访问循环计数器:
我们从一个读取寄存器的函数开始:
static inline unsigned int get_cyclecount (void)
unsigned int value;
// Read CCNT Register
asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));
return value;
您很可能还想重置和设置分隔线:
static inline void init_perfcounters (int32_t do_reset, int32_t enable_divider)
// in general enable all counters (including cycle counter)
int32_t value = 1;
// peform reset:
if (do_reset)
value |= 2; // reset all counters to zero.
value |= 4; // reset cycle counter to zero.
if (enable_divider)
value |= 8; // enable "by 64" divider for CCNT.
value |= 16;
// program the performance-counter control-register:
asm volatile ("MCR p15, 0, %0, c9, c12, 0\t\n" :: "r"(value));
// enable all counters:
asm volatile ("MCR p15, 0, %0, c9, c12, 1\t\n" :: "r"(0x8000000f));
// clear overflows:
asm volatile ("MCR p15, 0, %0, c9, c12, 3\t\n" :: "r"(0x8000000f));
do_reset
将循环计数器设置为零。就这么简单。
enable_diver
将启用 1/64 周期分频器。如果没有设置此标志,您将测量每个周期。启用它后,计数器每 64 个周期增加一次。如果您想测量会导致计数器溢出的长时间,这很有用。
使用方法:
// init counters:
init_perfcounters (1, 0);
// measure the counting overhead:
unsigned int overhead = get_cyclecount();
overhead = get_cyclecount() - overhead;
unsigned int t = get_cyclecount();
// do some stuff here..
call_my_function();
t = get_cyclecount() - t;
printf ("function took exactly %d cycles (including function call) ", t - overhead);
应该在所有 Cortex-A8 CPU 上工作..
哦 - 还有一些注意事项:
使用这些计数器,您将测量两次调用get_cyclecount()
之间的准确时间,包括在其他进程或内核中花费的所有时间。无法将测量限制在您的进程或单个线程中。
另外拨打get_cyclecount()
也不是免费的。它将编译为单个 asm 指令,但从协处理器移动会停止整个 ARM 流水线。开销非常高,可能会影响您的测量。幸运的是,开销也是固定的,所以你可以测量它并从你的时间中减去它。
在我的示例中,我对每次测量都这样做了。在实践中不要这样做。两次调用之间迟早会发生中断,并进一步扭曲您的测量。我建议您在空闲系统上测量几次开销,忽略所有外部因素并改用固定常数。
【讨论】:
亲爱的 Nils,再次感谢您如此快速而详细的回复。我想一步一步地采用这种方法,因为我想了解这一切是如何工作的,所以我从非常基础的水平开始。我以前没有在汇编中编程过,我不知道所有的先决条件,所以,请原谅我的无知。我编写了一个新的主文件,并在 main() 中包含了前两行,并使用 gcc 对其进行了编译。我没有编译错误,生成了最终的可执行文件,执行后我得到“非法指令”。这里有什么遗漏吗? Nils,我的 Linux 桌面系统上安装了 CodeSorcery g++ IDE(30 天试用版)。我想在那里构建我的项目(使用交叉编译器工具),然后在我的 i.MX515 板上使用可执行文件。我编写了您提到的程序,生成了可执行文件。我尝试调试(在模拟器上),但代码巫术抛出了一个错误,因为非法指令并且它停止了,无论如何它对我来说并不那么重要。我将可执行文件复制到我的 i.MX515 板上并尝试执行它,但又一次,我得到了 - 非法指令消息:((我已经编辑了问题) 我不知道操作系统运行的两种状态:内核和用户模式。我刚从同事那里得知。可能是我当前正在用户模式下运行,这就是为什么即使我的程序编译没有任何错误,我也会收到非法指令消息。 @vikramtheone,前两行必须从内核模式执行。它们启用对 CCNT(和相关)寄存器的用户模式访问。没有办法解决这个问题。在我看来,最简单的方法是编写一个超短内核模块来执行此操作。编译这些模块需要您在板上运行的内核的内核头文件,但是由于您使用的是 ubuntu,所以这应该不是什么大问题。这是一个最小的内核模块源代码:torus.untergrund.net/code/perfcnt_enable.c 您使用 make -C现在已经过去了几年,扩展 Nils 的答案! - 访问这些计数器的简单方法是build the kernel with gator。然后,它会报告用于Streamline 的计数器值,这是 ARM 的性能分析工具。
它将在时间轴上显示每个函数(为您提供系统性能的高级概览),准确显示执行所需的时间,以及占用的 CPU 百分比。您可以将此与您设置的每个计数器的图表进行比较,以收集和跟踪 CPU 密集型任务直至源代码级别。
Streamline 适用于所有 Cortex-A 系列处理器。
【讨论】:
【参考方案3】:我曾在具有指令级模拟器的 ARM7 工具链中工作。在其中运行应用程序可以为各个行和/或 asm 指令提供时间。这对于给定例程的微优化非常有用。不过,这种方法可能不适合整个应用程序/整个系统的优化。
【讨论】:
【参考方案4】:您需要在优化前后使用性能分析工具分析您的代码。
Acct 是一个命令行和一个函数,您可以使用它来监控您的资源。您可以通过 google 了解更多关于 acct 生成的 dat 文件的使用和查看。
我将使用其他开源性能分析工具更新这篇文章。
Gprof 是另一个这样的工具。请检查相同的文档。
【讨论】:
Praveen,我之前使用性能分析工具(例如 gprof)遇到的问题是,当我打开优化标志 (-O3) 时,我得到的统计数据没有任何意义。由于这个原因,我使用 gprof 已经有一段时间了,我现在试试看。 @vikramtheone - 假设您为每个函数调用创建一个 acct 文件,您可以获取在时间和其他参数方面使用的资源的详细信息。我用它来分析和比较功能级别的代码优化。或者,您可以使用 gettimeofday 访问 struct time_t 并获得微秒级别的函数执行时间。所以这取决于你想用它实现什么。 Praveen,我会调查一下账户。就 gettimeofday 而言,我目前正在使用,但我面临的问题是每次测量的时间波动很大,所以我认为直接时间测量不太合适,而是使用其他一些无论有多少进程正在运行,都将保持不变的实体更有用,这样的实体就是循环计数。至少到目前为止,我认为它会保持不变,不知道真相在等待什么。 @vikramtheone - 在这种情况下,您可以分析代码。您可以检查 getruusage()。 Acct 和 grof 等分析工具将为您提供执行时间场景的视图。但是,如果您能清楚地解释您所面临的不一致类型,您可以获得更好的答案,因为分析是发布前的一项主要活动。以上是关于如何测量 ARM Cortex-A8 处理器中的程序执行时间?的主要内容,如果未能解决你的问题,请参考以下文章
使用硬件计数器测量 ARM Cortex-A8 上的执行时间