Cómo medir el tiempo de ejecución del programa en el procesador ARM Cortex-A8?


Estoy usando un procesador basado en ARM Cortex-A8 llamado como i.MX515. Hay distribución Linux Ubuntu 9.10. Estoy ejecutando una aplicación muy grande escrita en C y estoy haciendo uso de las funciones gettimeofday(); para medir el tiempo que tarda mi aplicación.

main()

{

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

}

Este método era suficiente para ver qué bloques de mi aplicación estaba tomando qué cantidad de tiempo. Pero, ahora que, estoy tratando de optimizar mi código muy a través, con el método gettimeofday() de cálculo de tiempo, veo una gran cantidad de fluctuación entre corridas sucesivas (Corridas antes y después de mis optimizaciones), por lo que no puedo determinar los tiempos de ejecución reales, de ahí el impacto de mis mejoras.

¿Puede alguien sugerirme lo que debo hacer?

Si accediendo al contador de ciclos ( Idea sugerida en el sitio web de ARM para Cortex-M3) ¿puede alguien indicarme algún código que me indique los pasos que debo seguir para acceder a los registros del temporizador en Cortex-A8?

Si este método no es muy preciso, entonces por favor, sugiera algunas alternativas.

Gracias


Seguimientos

Seguimiento 1: Escribió el siguiente programa en el Código Sorcery, se generó el ejecutable que cuando intenté ejecutar en el tablero, obtuve - Mensaje de instrucción ilegal:(

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;
}

Follow up 2: Yo había escrito a Freescale para el apoyo y me han enviado de vuelta la siguiente respuesta y un programa (No entendí mucho de ella)

Aquí está en qué podemos ayudarle ahora mismo: Te estoy enviando adjuntar un ejemplo de código, que envía un stream usando el UART, desde lo que tu código, parece que no estás init correctamente el 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) ;
}
Author: starblue, 2010-07-14

4 answers

Acceder a los contadores de rendimiento no es difícil, pero debe habilitarlos desde el modo kernel. Por defecto, los contadores están deshabilitados.

En pocas palabras, debe ejecutar las siguientes dos líneas dentro del núcleo. Ya sea como un módulo cargable o simplemente agregando las dos líneas en algún lugar de la placa-init hará:

  /* 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));

Una vez hecho esto, el contador de ciclos comenzará a incrementarse para cada ciclo. Los desbordamientos del registro pasarán desapercibidos y no causarán ningún problema (excepto que podrían estropear sus medidas).

Ahora desea acceder al contador de ciclos desde el modo usuario:

Empezamos con una función que lee el registro:

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;
}

Y lo más probable es que desee restablecer y establecer el divisor también:

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 pondrá el contador de ciclos a cero. Tan fácil como eso.

enable_diver habilitará el divisor de ciclo 1/64. Sin esta bandera, estarás midiendo cada ciclo. Con ello habilitado el contador se incrementa por cada 64 ciclo. Esto es útil si desea medir tiempos largos que de otro modo causarían que el contador se desbordara.

Cómo usarlo:

  // 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);

Debería funcionar en todas las CPU Cortex-A8..

Oh-y algunas notas:

Usando estos contadores medirá el tiempo exacto entre las dos llamadas a get_cyclecount() incluyendo todo lo gastado en otros procesos o en el núcleo. No hay forma de restringir la medición a su proceso o a un solo hilo.

También llamar a get_cyclecount() no es libre. Se compilará en una sola instrucción asm, pero los movimientos desde el coprocesador detendrán toda la canalización ARM. La sobrecarga es bastante alta y puede sesgar su medición. Afortunadamente, la sobrecarga también es fija, por lo que puede medirla y restarla de sus tiempos.

En mi ejemplo hice eso para cada medida. No hagas esto en la práctica. Tarde o temprano se producirá una interrupción entre las dos llamadas y sesgará aún más sus mediciones. Le sugiero que mida la sobrecarga un par de veces en un sistema inactivo, ignore a todos los forasteros y use una constante fija en su lugar.

 44
Author: Nils Pipenbrinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-07 14:44:37

Necesita perfilar su código con herramientas de análisis de rendimiento antes y después de sus optimizaciones.

Acct es una línea de comandos y una función que puede usar para monitorear sus recursos. Puede buscar más en Google sobre el uso y la visualización del archivo dat generado por acct.

Actualizaré este post con otras herramientas de análisis de rendimiento de código abierto.

Gprof es otra herramienta de este tipo. Por favor revise la documentación para el mismo.

 1
Author: Praveen S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-14 15:02:54

Para ampliar la respuesta de Nils ahora que han transcurrido un par de años! - una forma fácil de acceder a estos contadores es construir el núcleo con gator. Esto luego informa los valores de contador para su uso con Streamline, que es la herramienta de análisis de rendimiento de ARM.

Se mostrará cada función en una línea de tiempo (que le da una visión general de alto nivel de cómo su sistema está funcionando), que le muestra exactamente cuánto tiempo tomó para ejecutarse, junto con % CPU que ha tomado. Puedes compare esto con los gráficos de cada contador que haya configurado para recopilar y seguir las tareas intensivas de la CPU hasta el nivel de código fuente.

Streamline funciona con todos los procesadores de la serie Cortex-A.

 1
Author: Badmanton Casio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-07 11:17:10

He trabajado en una cadena de herramientas para ARM7 que tenía un simulador de nivel de instrucción. Ejecutar aplicaciones que podrían dar tiempos para líneas individuales y / o instrucciones asm. Eso fue genial para una micro optimización de una rutina dada. Sin embargo, ese enfoque probablemente no sea apropiado para una aplicación completa/optimización de todo el sistema.

 0
Author: Digikata,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-14 15:09:15