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

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

 main() { gettimeofday(start); .... .... .... gettimeofday(end); } 

Este método fue suficiente para ver qué bloques de mi aplicación tomaban la cantidad de tiempo. Pero, ahora que estoy tratando de optimizar mi código muy de cerca, con el método gettimeofday () para calcular el tiempo, veo mucha fluctuación entre ejecuciones sucesivas (Ejecutar antes y después de mis optimizaciones), así que no puedo para determinar los tiempos de ejecución reales, de ahí el impacto de mis mejoras.

¿Alguien puede sugerirme lo que debería hacer?

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

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

Gracias


Seguimientos

Seguimiento 1: Escribió el siguiente progtwig en Code Sorcery, se generó el ejecutable que cuando intenté ejecutar en la placa, recibí – 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; } 

Seguimiento 2: había escrito a Freescale para obtener asistencia y me han enviado la siguiente respuesta y un progtwig (no entendí mucho de eso)

Aquí es con lo que podemos ayudarle en este momento: le envío adjuntar un ejemplo de código, que envía una secuencia usando el UART, de lo que su código, parece que no está iniciando correctamente la MPU.

 (hash)include  (hash)include  (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  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) ; } 

El acceso 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 kernel. 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 que haya 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 mediciones).

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

Comenzamos 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 configurar 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 establecerá el contador de ciclos en cero. Tan fácil como eso.

enable_diver habilitará el divisor de ciclo 1/64. Sin este conjunto de banderas, estarás midiendo cada ciclo. Con esto habilitado, el contador aumenta por cada 64 ciclos. Esto es útil si desea medir tiempos prolongados que de lo contrario harían que el contador se desborde.

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.

Ah, 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 kernel. No hay forma de restringir la medición a su proceso o un solo hilo.

También llamar a get_cyclecount() no es gratis. Se comstackrá en una sola instrucción asm, pero los movimientos del coprocesador detendrán toda la tubería de ARM. La sobrecarga es bastante alta y puede sesgar su medida. Afortunadamente, la sobrecarga también es fija, por lo que puedes medirla y restarla de tus tiempos.

En mi ejemplo, hice eso para cada medición. No hagas esto en la práctica. Tarde o temprano ocurrirá una interrupción entre las dos llamadas y sesgará aún más sus medidas. 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.

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

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

Actualizaré esta publicación 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.

¡Ampliar la respuesta de Nils ahora que han pasado un par de años! – una manera fácil de acceder a estos contadores es construir el kernel con Gator . Esto luego informa los valores del contador para su uso con Streamline , que es la herramienta de análisis de rendimiento de ARM.

Mostrará cada función en una línea de tiempo (ofreciéndole una descripción general de alto nivel de cómo está funcionando su sistema), mostrándole exactamente cuánto tiempo llevó ejecutar, junto con el% de CPU que ha llevado a cabo. Puede comparar esto con los gráficos de cada contador que haya configurado para recostackr y seguir tareas intensivas de la CPU hasta el nivel del código fuente.

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

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

Intereting Posts