¿Es posible monitorear un bloque de código y determinar la cantidad de ciclos de reloj del procesador que tomó el código en un procesador atmel Arduino y/o AVR? o, ¿debo monitorear los microsegundos que pasan antes y después de ejecutar el código? Nota: no me preocupa el tiempo real (como cuántos segundos reales pasaron) tanto como "cuántos ciclos de reloj requiere este código de la CPU"
La solución actual que se me ocurre es de time.c:
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
cableado.c agrega:
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
Por esta cuenta, podría calcular los ciclos de reloj pasados al monitorear los microsegundos pasados y luego pasar eso a microsegundos a ciclos de reloj (). Mi pregunta es, ¿hay una mejor manera?
nota al margen: ¿existen buenos recursos para monitorear el rendimiento del AVR? Las búsquedas en lmgtfy.com y en varios foros no proporcionan ningún resultado obvio, aparte de la exploración de temporizadores.
Gracias
El método más simple es hacer que su código levante un pin antes de ejecutar el código que desea cronometrar, y lo baje después de que haya terminado de hacer lo que sea. Luego haga el bucle de código (o use un osciloscopio digital con memoria en modo de disparo único) y solo mida el alcance y luego pin. La longitud del pulso le dice cuánto tiempo tomó ejecutar el código más un ciclo de reloj desde que cambió el estado del pin (creo que toma un ciclo, no estoy 100% seguro).
¿A qué te refieres con "monitorear"?
No debería ser difícil contar los ciclos de reloj para AVR para pequeñas piezas de código ensamblador.
También puede configurar un puerto antes de que se ejecute el código y restablecerlo después, y monitorearlo con un analizador lógico o un osciloscopio para obtener la sincronización.
Y también podrías leer el tiempo de un temporizador de marcha rápida, como dices.
Este es un ejemplo de Arduino usando la función clockCyclesPerMicrosecond() para calcular los relojes que han pasado. Este código esperará 4 segundos, luego imprimirá el tiempo transcurrido desde el inicio del programa. Los 3 valores de la izquierda son el tiempo total (microsegundos, milisegundos, ciclos de reloj totales) y los 3 de la derecha son los tiempos transcurridos:
Producción:
clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002 64051776 :: 4003236 4002 64051760
8006668 8006 128106688 :: 4003432 4004 64054912
12010508 12010 192168128 :: 4003840 4004 64061440
16014348 16014 256229568 :: 4003840 4004 64061440
20018188 20018 320291008 :: 4003840 4004 64061440
24022028 24022 384352448 :: 4003840 4004 64061440
28026892 28026 448430272 :: 4004864 4004 64077824
32030732 32030 512491712 :: 4003840 4004 64061440
36034572 36034 576553152 :: 4003840 4004 64061440
40038412 40038 640614592 :: 4003840 4004 64061440
44042252 44042 704676032 :: 4003840 4004 64061440
48046092 48046 768737472 :: 4003840 4004 64061440
52050956 52050 832815296 :: 4004864 4004 64077824
Estoy seguro de que hay una explicación razonable de por qué los primeros bucles también tenían ciclos de reloj transcurridos más cortos que la mayoría y por qué todos los demás bucles alternan entre dos longitudes de ciclos de reloj.
Código:
unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
Serial.begin(9600);
}
boolean firstloop=1;
void loop() {
delay(4000);
if (firstloop) {
Serial.print("clocks for 1us:");
ck=microsecondsToClockCycles(1);
Serial.println(ck,DEC);
firstloop--;
Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
}
_us=us;
_ms=ms;
_ck=ck;
us=micros(); // us since program start
ms=millis();
//ms=us/1000;
ck=microsecondsToClockCycles(us);
Serial.print(us,DEC);
Serial.print("\t");
Serial.print(ms,DEC);
Serial.print("\t");
Serial.print(ck,DEC);
Serial.print("\t::\t");
__us = us - _us;
__ms = ms - _ms;
__ck = ck - _ck;
Serial.print(__us,DEC);
Serial.print("\t");
Serial.print(__ms,DEC);
Serial.print("\t");
Serial.println(__ck,DEC);
}
Nota al margen: si elimina el retraso de 4 segundos, comenzará a ver los efectos de Serial.print () mucho más claramente. Tenga en cuenta que aquí se comparan 2 ejecuciones. Solo he incluido 4 muestras cercanas entre sí de sus respectivos registros.
Ejecutar 1:
5000604 5000 80009664 :: 2516 2 40256
6001424 6001 96022784 :: 2520 3 40320
7002184 7002 112034944 :: 2600 3 41600
8001292 8001 128020672 :: 2600 3 41600
Ejecutar 2:
5002460 5002 80039360 :: 2524 3 40384
6000728 6000 96011648 :: 2520 2 40320
7001452 7001 112023232 :: 2600 3 41600
8000552 8000 128008832 :: 2604 3 41664
El tiempo transcurrido aumenta sobre el tiempo total de ejecución. Después de transcurrido un segundo, los relojes aumentan en promedio de 40k a 44k. Esto sucede constantemente unos pocos milisegundos después de 1 segundo y los relojes transcurridos permanecen alrededor de 44k durante al menos los próximos 10 segundos (no lo he probado más). Esta es la razón por la cual el monitoreo es útil o necesario. ¿Quizás la disminución de la eficiencia tiene que ver con la configuración o errores en serie? O tal vez el código no usa la memoria correctamente y tiene una fuga que afecta el rendimiento, etc.
Dado que cada línea de código agregada a su fuente tendrá un impacto en el rendimiento y podría cambiar las optimizaciones aplicadas. Los cambios deben ser los mínimos necesarios para realizar la tarea.
Acabo de encontrar un complemento de Atmel Studio llamado "Depurador de archivo de ensamblaje anotado". http://www.atmel.com/webdoc/aafdebugger/pr01.html Parece como recorrer paso a paso el lenguaje ensamblador generado real, mientras que probablemente tedioso le mostrará exactamente lo que está sucediendo. Es posible que aún tenga que decodificar cuántos ciclos se necesitan para cada instrucción, pero se acercaría mucho más que algunas de las otras opciones publicadas.
Para aquellos que no lo saben, en la carpeta Salida de su proyecto hay un archivo con una extensión LSS. Este archivo contiene todo su código fuente original como comentarios y debajo de cada línea está el lenguaje ensamblador que se generó en base a esa línea de código. La generación del archivo LSS se puede desactivar, así que verifique la siguiente configuración.
Propiedades del proyecto | Cadena de herramientas | AVR/GNU Común | Archivos de salida
Casilla de verificación ".lss (Generar archivo lss)
Puede usar uno de los temporizadores incorporados. Configura todo para precaller=1 y TCNT=0 antes del bloque. Luego habilite el temporizador en la línea antes del bloque y desactívelo en la línea después del bloque. El TCNT ahora retendrá la cantidad de ciclos que tomó el bloque, menos los ciclos fijos para el código de habilitación y deshabilitación.
Tenga en cuenta que el TNCT se desbordará después de 65535 ciclos de reloj en un temporizador de 16 bits. Puede usar el indicador de desbordamiento para duplicar el tiempo de ejecución. Si aún necesita más tiempo, puede usar un preescalador, pero obtendrá menos resolución.
cyphunk