在ARM Cortex-A8处理器中如何测量程序执行时间?

30

我使用的是名为i.MX515的基于ARM Cortex-A8的处理器。它运行着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) ;
}

1
您可以始终在大量运行中获取平均执行时间。 - caf
ARM11和Cortex-A/R的性能监视器示例代码:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka4237.html - 0x90
4个回答

53

访问性能计数器并不困难,但是您需要从内核模式启用它们。默认情况下,计数器处于禁用状态。

简而言之,您需要在内核中执行以下两行代码。无论是作为可加载模块,还是只是将这两行代码添加到 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()并不是免费的。它将编译为单个汇编指令,但来自协处理器的移动将使整个ARM流水线停顿。开销相当高,可能会影响您的测量结果。幸运的是,开销也是固定的,因此您可以测量它并从您的时间中减去它。
在我的示例中,我对每个测量都这样做了。实际上请勿这样操作。在两个调用之间,中断迟早会发生,并进一步扭曲您的测量结果。我建议您在空闲系统上多次测量开销,忽略所有外部因素,并使用固定常数。

亲爱的尼尔斯, 再次感谢您如此快速和详细的回复。我想逐步采取这种方法,因为我希望学习所有这些工作原理,所以我从非常基本的水平开始。我以前没有编写过汇编程序,也不知道所有的先决条件,所以请容忍我的无知。我编写了一个新的主文件,并在main(){}中包含了前两行,然后使用gcc编译它。我没有编译错误,生成了最终的可执行文件,但在执行时出现了“非法指令”。我错过了什么吗? - HaggarTheHorrible
3
@vikramtheone,前两行必须在内核模式下执行。它们允许用户模式访问CCNT(和相关)寄存器。没有其他方法。我认为最简单的方法是编写一个超短的内核模块来完成此操作。编译这些模块需要你所用开发板上运行的内核的内核头文件,但由于你使用的是Ubuntu,这应该不是大问题。这里是一个最小化的内核模块源代码:http://torus.untergrund.net/code/perfcnt_enable.c - Nils Pipenbrinck
3
你需要在目标系统上使用 make -C <path-to-kernel-source> SUBDIRS=$(PWD) modules 命令进行编译(注意:在目标系统上)。这样应该会生成一个名为 perfcnt_enable.ko 的文件,你可以使用 insmod ./perfcnt_enable.ko 命令在目标系统上加载它。使用 dmesg 命令可以查看它是否成功加载。 - Nils Pipenbrinck
@NilsPipenbrinck 很棒的回答,你是否熟悉一个处理所有性能计数器的库/源代码? - 0x90
如果有人对如何构建和运行完整指南感兴趣--它在这个答案中。 - Sam Protsenko
显示剩余6条评论

1

在进行优化之前和之后,您需要使用性能分析工具对代码进行分析。

Acct是一个命令行和函数,您可以使用它来监视资源。您可以通过Google了解有关使用和查看由acct生成的dat文件的更多信息。

我将在此帖子中更新其他开源性能分析工具。

Gprof是另一种类似的工具。请查阅相关文档。


Praveen,我以前使用性能分析工具(例如gprof)时遇到的问题是,当我打开优化标志(-O3)时,我得到的统计数据毫无意义。因为这个原因,我已经有一段时间没有使用gprof了,现在我会再试一下看看。 - HaggarTheHorrible
假设您每次函数调用都创建一个acct文件,您可以获得有关时间和其他参数使用的资源的详细信息。我已经使用它来在函数级别上进行分析和比较代码优化。 或者,您可以使用gettimeofday访问struct time_t,并以微秒级别获取函数执行时间。 因此,这取决于您想要实现什么。 - Praveen S
Praveen,我会查看acct。就gettimeofday而言,目前我正在使用它,但我面临的问题是每次测量的时间存在很多波动,因此我认为直接时间测量不太合适,而使用一些其他实体更有用,这些实体无论运行多少个进程都将保持不变,这样的实体是循环计数。至少目前我认为它将保持不变,不知道真相如何。 - HaggarTheHorrible
@vikramtheone - 如果是这样,您可以对代码进行分析。您可以了解getruusage()的相关信息。像Acct和grof这样的分析工具将为您提供执行时间情况的视图。但是,如果您能清楚地解释您面临的不一致类型,那么在发布之前进行分析是一个重要的活动,您可以获得更好的答案。 - Praveen S

1
现在补充一下Nils的答案,几年过去了!- 一种访问这些计数器的简单方法是使用gator构建内核。然后,它会报告用于Streamline的计数器值,这是ARM的性能分析工具。
它将在时间轴上显示每个函数(为您提供系统性能的高级概述),并显示执行所需的确切时间,以及它所占用的% CPU。您可以将其与设置要收集的每个计数器的图表进行比较,并跟踪CPU密集型任务到源代码级别。
Streamline适用于所有Cortex-A系列处理器。

0

我曾在一个ARM7工具链中工作过,其中包含了指令级模拟器。在其中运行应用程序可以给出每行代码和/或汇编指令的时间。这对于针对特定例程进行微观优化非常有用。然而,这种方法可能不适用于整个应用程序/整个系统的优化。


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