web-dev-qa-db-fra.com

Comment mesurer le temps d'exécution du programme en ARM Processeur Cortex-A8?

J'utilise un processeur basé sur ARM Cortex-A8 appelé i.MX515. Il existe une distribution Linux Ubuntu 9.10. J'utilise une très grosse application écrite en C et je me sers des fonctions gettimeofday(); pour mesurer le temps que prend mon application.

main()

{

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

}

Cette méthode était suffisante pour déterminer quels blocs de mon application prenaient quel temps. Mais, maintenant que j'essaye d'optimiser mon code de manière très complète, avec la méthode de calcul du temps gettimeofday (), je vois beaucoup de fluctuations entre les exécutions successives (Exécuter avant et après mes optimisations), je ne peux donc pas pour 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 cycles ( Idée suggérée sur le site Web de ARM pour Cortex-M3 ), quelqu'un peut-il m'indiquer un code qui indique les étapes à suivre pour accéder aux registres de la minuterie sur Cortex -A8 ?

Si cette méthode n'est pas très précise, veuillez suggérer des alternatives.

Merci


Suivis

Suivi 1: Écrit le programme suivant sur Code Rituel, l'exécutable a été généré. Lorsque j'ai essayé de courir sur le tableau, j'ai: - Message d'instruction illégale :(

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

Suivi 2: J'avais écrit à Freescale pour obtenir de l'aide et ils m'ont renvoyé la réponse suivante et un programme (je n'y ai pas très bien compris)

Voici ce que nous pouvons vous aider pour le moment: Je vous envoie un exemple de code, qui envoie un flux en utilisant l'UART, à partir de quel code, il semble que vous n'ayez pas initié correctement 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 %x\n\r", k ) ;
        serv_WDOG() ;
        led() ;

    }

    return(1) ;
}
27
HaggarTheHorrible

L'accès aux compteurs de performance n'est pas difficile, mais vous devez les activer à partir du mode noyau. Par défaut, les compteurs sont désactivés. 

En un mot, vous devez exécuter les deux lignes suivantes dans le noyau. Soit en tant que module chargeable, soit en ajoutant simplement les deux lignes quelque part dans 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));

Une fois que vous avez fait cela, le compteur de cycle commence à s'incrémenter pour chaque cycle. Les débordements du registre passeront inaperçus et ne poseront aucun problème (sauf qu'ils risquent de gâcher vos mesures).

Maintenant, vous voulez accéder au compteur de cycles à partir du mode utilisateur: 

Nous commençons avec 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 souhaiterez probablement réinitialiser et définir également le séparateur:

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 mettra le compteur de cycles à zéro. Aussi simple que ça.

enable_diver activera le diviseur de 1/64 cycle. Sans cet indicateur, vous mesurerez chaque cycle. Avec cette option activée, le compteur augmente tous les 64 cycles. Ceci est utile si vous souhaitez mesurer des temps longs qui pourraient sinon provoquer le débordement du 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 a été 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.

Aussi, appeler get_cyclecount() n'est pas gratuit. Il compilera en une seule instruction asm, mais passer du co-processeur bloquera 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 corrigés, vous pouvez donc les mesurer et les soustraire de vos horaires. 

Dans mon exemple, je l'ai fait pour chaque mesure. Ne faites pas cela dans la pratique. Une interruption surviendra tôt ou tard entre les deux appels et faussera encore plus vos mesures. Je vous suggère de mesurer le temps système plusieurs fois sur un système inactif, d'ignorer tous les étrangers et d'utiliser plutôt une constante fixe.

45
Nils Pipenbrinck

Pour approfondir la réponse de Nils maintenant que quelques années se sont écoulées! - un moyen facile d’accéder à ces compteurs consiste à construire le noyau avec gator . Ceci rapporte ensuite les valeurs des compteurs à utiliser avec Streamline , outil d'analyse des performances d'ARM.

Il affichera chaque fonction sur une timeline (vous donnant une vue d'ensemble de la performance de votre système), en vous indiquant exactement le temps qu'il a fallu pour exécuter, ainsi que le pourcentage de CPU qu'il a pris. Vous pouvez comparer cela aux graphiques de chaque compteur que vous avez configuré pour collecter et suivre des tâches gourmandes en ressources CPU jusqu'au niveau du code source.

Streamline fonctionne avec tous les processeurs de la série Cortex-A.

1
Badmanton Casio

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 surveiller vos ressources. Vous pouvez en rechercher davantage sur l'utilisation et la visualisation du fichier dat généré par acct.

Je mettrai à jour ce post avec d'autres outils d'analyse de performances opensource.

Gprof est un autre outil de ce type. Veuillez vérifier la documentation pour la même chose.

1
Praveen S

J'ai travaillé dans une chaîne d'outils pour ARM7 qui disposait d'un simulateur de niveau instruction. L'exécution d'applications dans cela pourrait donner des temps pour des lignes individuelles et/ou des instructions asm. C'était formidable pour une micro optimisation d'une routine donnée. Cette approche ne convient probablement pas pour l'optimisation d'une application/d'un système entier.

0
Digikata