Comment mesurer le temps d'exécution du programme dans le processeur ARM Cortex-A8?
J'utilise un processeur basé sur ARM Cortex-A8 appelé I. MX515. il existe une distribution Linux Ubuntu 9.10. J'exécute une très grosse application écrite en C et j'utilise les fonctions gettimeofday();
pour mesurer le temps que prend mon application.
main()
{
gettimeofday(start);
....
....
....
gettimeofday(end);
}
Cette méthode était suffisante pour regarder quels blocs de mon application prenait quelle quantité de temps. Mais, maintenant que, j'essaie d'optimiser mon code très complètement, avec la méthode gettimeofday() de calcul du temps, je vois beaucoup de fluctuation entre les exécutions successives (exécutées avant et après mes optimisations), Je ne suis donc pas en mesure de déterminer les temps d'exécution réels, d'où l'impact de mes améliorations.
Quelqu'un peut-il me suggérer ce que je devrais faire?
Si en accédant au compteur de cycle (idée suggérée sur le site web ARM pour Cortex-M3 ) quelqu'un peut-il me pointer vers un code qui me donne les étapes que je dois suivre pour accéder aux registres de minuteriesur Cortex-A8 ?
Si cette méthode n'est pas très précis veuillez suggérer quelques solutions de rechange.
Merci
Suivi
Suivi 1: a écrit le programme suivant sur Code Sorcery, l'exécutable a été généré qui quand j'ai essayé de courir sur le tableau, j'ai eu - message D'instruction illégal:(
static inline unsigned int get_cyclecount (void)
{
unsigned int value;
// Read CCNT Register
asm volatile ("MRC p15, 0, %0, c9, c13, 0tn": "=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, 0tn" :: "r"(value));
// enable all counters:
asm volatile ("MCR p15, 0, %0, c9, c12, 1tn" :: "r"(0x8000000f));
// clear overflows:
asm volatile ("MCR p15, 0, %0, c9, c12, 3tn" :: "r"(0x8000000f));
}
int main()
{
/* enable user-mode access to the performance counter*/
asm ("MCR p15, 0, %0, C9, C14, 0nt" :: "r"(1));
/* disable counter overflow interrupts (just in case)*/
asm ("MCR p15, 0, %0, C9, C14, 2nt" :: "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;
}
Suivi 2: j'avais écrit à Freescale pour obtenir du soutien et ils m'ont renvoyé la réponse suivante et un programme (Je n'en comprenais pas beaucoup)
Voici ce que nous pouvons vous aider dès maintenant: Je vous envoie joindre un exemple de code, qui envoie un flux en utilisant L'UART, à partir de ce que votre code, il semble que vous n'êtes pas correctement initialisé le 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 %xnr", k ) ;
serv_WDOG() ;
led() ;
}
return(1) ;
}
4 réponses
L'accès aux compteurs de performances n'est pas difficile, mais vous devez les activer en mode noyau. Par défaut, les compteurs sont désactivés.
En un mot, vous devez exécuter les deux lignes suivantes à l'intérieur du noyau. Soit en tant que module chargeable, soit en ajoutant simplement les deux lignes quelque part dans la carte-init fera l'affaire:
/* 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));
Une fois que vous avez fait cela, le compteur de cycle commencera à s'incrémenter pour chaque cycle. Les débordements du registre passeront inaperçus et ne poseront aucun problème (sauf qu'ils pourraient gâcher vos mesures).
Maintenant, vous voulez accéder au compteur de cycles à partir du mode utilisateur:
Nous commençons par une fonction qui lit le registre:
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;
}
Et vous voulez probablement réinitialiser et définir le diviseur aussi:
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
réglera le compteur de cycle à zéro. C'est très simple.
enable_diver
permettra le diviseur de cycle 1/64. Sans ce jeu de drapeaux, vous mesurerez chaque cycle. Avec elle activée le compteur est augmenté pour chaque 64 cycle. Ceci est utile si vous voulez mesurer de longues périodes qui, autrement, feraient déborder le compteur.
Comment l'utiliser:
// 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);
Devrait fonctionner sur tous les processeurs Cortex-A8..
Oh-et quelques notes:
En utilisant ces compteurs, vous mesurerez le temps exact entre les deux appels à get_cyclecount()
, y compris tout ce qui est dépensé dans d'autres processus ou dans le noyau. Il n'y a aucun moyen de limiter la mesure à votre processus ou à un seul thread.
Appeler aussi get_cyclecount()
n'est pas gratuit. Il compilera en une seule instruction asm, mais les mouvements du co-processeur bloqueront tout le pipeline ARM. Les frais généraux sont assez élevés et peuvent fausser votre mesure. Heureusement, les frais généraux sont également fixes, de sorte que vous pouvez le mesurer et le soustraire de vos timings.
Dans mon exemple, je l'ai fait pour chaque mesure. Ne le faites pas dans la pratique. Une interruption se produira tôt ou tard entre les deux appels et biaisera encore plus vos mesures. Je vous suggère de mesurer le overhead plusieurs fois sur un système inactif, ignorez tous les étrangers et utilisez une constante fixe à la place.
Vous devez profiler votre code avec des outils d'analyse des performances avant et après vos optimisations.
Acct est une ligne de commande et une fonction que vous pouvez utiliser pour gérer vos ressources. Vous pouvez google plus sur l'utilisation et la visualisation du fichier DAT donc généré par acct.
Je vais mettre à jour ce post avec d'autres outils d'analyse des performances opensource.
Gprof est un autre outil de ce type. Consultez la documentation pour la même chose.
Pour développer la réponse de Nils maintenant que quelques années se sont écoulées! - un moyen facile d'accéder à ces compteurs est de compiler le noyau avec gator. Cela rapporte ensuite les valeurs de compteur à utiliser avec Streamline , qui est L'outil D'analyse des performances D'ARM.
Il affichera chaque fonction sur une timeline (vous donnant un aperçu de haut niveau de la performance de votre système), vous montrant exactement combien de temps il a fallu pour exécuter, avec % CPU qu'il a pris. Vous pouvez comparez cela avec les graphiques de chaque compteur que vous avez configuré pour collecter et suivre les tâches gourmandes en CPU jusqu'au niveau du code source.
Streamline fonctionne avec tous les processeurs Cortex-a series.
J'ai travaillé dans une chaîne D'outils pour ARM7 qui avait un simulateur de niveau d'instruction. Exécuter des applications qui pourraient donner des timings pour des lignes individuelles et / ou des instructions asm. C'était génial pour une micro optimisation d'une routine donnée. Cette approche n'est probablement pas appropriée pour une application entière/optimisation du système entier.