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

Hag*_*ble 30 c arm performancecounter time-measurement cortex-a8

我正在使用一个名为i.MX515的基于ARM Cortex-A8的处理器.有Linux Ubuntu 9.10发行版.我正在运行一个用C编写的非常大的应用程序,我正在利用gettimeofday();函数来衡量我的应用程序所需的时间.

main()

{

gettimeofday(start);
....
....
....
gettimeofday(end);

}
Run Code Online (Sandbox Code Playgroud)

这个方法足以让我看看我的应用程序块占用了多少时间.但是,现在,我正在尝试使用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;
}
Run Code Online (Sandbox Code Playgroud)

跟进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) ;
}
Run Code Online (Sandbox Code Playgroud)

Nil*_*nck 50

访问性能计数器并不困难,但您必须从内核模式启用它们.默认情况下,计数器被禁用.

简而言之,您必须在内核中执行以下两行.无论是作为可加载模块还是只是在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));
Run Code Online (Sandbox Code Playgroud)

一旦你这样做,循环计数器将开始递增每个循环.寄存器的溢出将被忽视并且不会引起任何问题(除非它们可能弄乱您的测量).

现在您想要从用户模式访问循环计数器:

我们从一个读取寄存器的函数开始:

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;
}
Run Code Online (Sandbox Code Playgroud)

你很可能也希望重置并设置分隔符:

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));
}
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

应该适用于所有Cortex-A8 CPU ..

哦 - 还有一些说明:

使用这些计数器,您将测量两次调用之间的确切时间,get_cyclecount()包括在其他进程或内核中花费的所有内容.没有办法将测量限制在您的过程或单个线程中.

打电话get_cyclecount()也不是免费的.它将编译为单个asm指令,但从协处理器移动将停止整个ARM管道.开销很高,可能会使您的测量偏差.幸运的是,开销也是固定的,因此您可以测量它并从您的时间中减去它.

在我的例子中,我为每次测量都做了.不要在实践中这样做.两次呼叫之间迟早会发生中断,甚至会进一步扭曲您的测量值.我建议您在空闲系统上测量几次开销,忽略所有局外人并使用固定常量.

  • @vikramtheone,前两行必须从内核模式执行.它们允许用户模式访问CCNT(和相关)寄存器.没有办法解决这个问题.在我看来,最简单的方法是编写一个执行此操作的超短内核模块.编译这些模块需要你在你的主板上运行的内核的内核头文件,但是因为你使用的ubuntu应该不是一个大问题.这是一个最小的内核模块源:http://torus.untergrund.net/code/perfcnt_enable.c (3认同)
  • 你使用make -C <path-to-kernel-source> SUBDIRS = $(PWD)模块编译它(在目标上!).这应该生成一个名为perfcnt_enable.ko的文件,您可以使用insmod ./perfcnt_enable.ko加载(在目标上).dmesg会告诉你它是否有效. (3认同)