webentwicklung-frage-antwort-db.com.de

Wie misst man die Programmausführungszeit in ARM Cortex-A8-Prozessor?

Ich verwende einen ARM Cortex-A8-basierten Prozessor, der als i.MX515 bezeichnet wird. Es gibt Linux Ubuntu 9.10 Distribution. Ich verwende eine sehr große Anwendung, die in C geschrieben ist, und benutze Funktionen von gettimeofday();, um die Zeit zu messen, die meine Anwendung benötigt.

main()

{

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

}

Diese Methode reichte aus, um zu sehen, welche Blöcke meiner Anwendung wie viel Zeit beanspruchten. Aber jetzt, da ich versuche, meinen Code sehr gründlich zu optimieren, mit der Methode gettimeofday () zur Zeitberechnung, sehe ich eine große Fluktuation zwischen aufeinanderfolgenden Läufen (Ausführen vor und nach meinen Optimierungen), sodass ich nicht in der Lage bin um die tatsächlichen Ausführungszeiten zu bestimmen, daher die Auswirkungen meiner Verbesserungen.

Kann mir jemand vorschlagen, was ich tun soll?

Wenn ich durch den Zugriff auf den Zykluszähler ( Idee auf ARM - Website für Cortex-M3 ) auf irgendeinen Code verweisen kann, der mir die Schritte angibt, die ich befolgen muss, um auf die Timer zuzugreifen auf Cortex-A8 ?

Wenn diese Methode nicht sehr genau ist, schlagen Sie bitte einige Alternativen vor.

Vielen Dank


Follow-Ups

Follow-up 1: Schrieb folgendes Programm in Code Sorcery, die ausführbare Datei wurde generiert, die ich beim Ausführen auf dem Board bekam - Illegal Anweisungsnachricht :(

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: Ich hatte an Freescale geschrieben, um Unterstützung zu erhalten, und sie haben mir die folgende Antwort und ein Programm zurückgeschickt. (Ich habe nicht viel davon verstanden)

Hier können wir Ihnen jetzt helfen: Ich sende Ihnen ein Beispiel für Code, der einen Stream mithilfe des UART sendet. Daraus ergibt sich, dass Ihr Code anscheinend nicht korrekt von der MPU ist. 

(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

Der Zugriff auf die Leistungsindikatoren ist nicht schwierig, aber Sie müssen sie im Kernel-Modus aktivieren. Standardmäßig sind die Zähler deaktiviert. 

Kurz gesagt, müssen Sie die folgenden zwei Zeilen innerhalb des Kernels ausführen. Entweder als ladbares Modul oder einfach durch Hinzufügen der zwei Zeilen irgendwo im 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));

Sobald Sie dies getan haben, beginnt der Zykluszähler für jeden Zyklus zu inkrementieren. Überläufe des Registers bleiben unbemerkt und verursachen keine Probleme (es sei denn, sie können Ihre Messungen stören).

Nun möchten Sie aus dem Benutzermodus auf den Zykluszähler zugreifen: 

Wir beginnen mit einer Funktion, die das Register liest:

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

Wahrscheinlich möchten Sie auch den Teiler zurücksetzen und einstellen:

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 setzt den Zykluszähler auf Null. So einfach ist das.

enable_diver aktiviert den 1/64 Zyklusteiler. Ohne dieses Flag werden Sie jeden Zyklus messen. Wenn diese Option aktiviert ist, wird der Zähler alle 64 Zyklen erhöht. Dies ist nützlich, wenn Sie lange Zeiten messen möchten, die sonst dazu führen, dass der Zähler überläuft.

Wie man es benutzt:

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

Sollte auf allen Cortex-A8-CPUs funktionieren.

Oh - und einige Notizen:

Mit diesen Indikatoren messen Sie die genaue Zeit zwischen den beiden Aufrufen von get_cyclecount(), einschließlich aller in anderen Prozessen oder im Kernel aufgewendeten Aufrufe. Es gibt keine Möglichkeit, die Messung auf Ihren Prozess oder einen einzelnen Thread zu beschränken.

Auch das Aufrufen von get_cyclecount() ist nicht kostenlos. Es wird zu einer einzelnen ASM-Anweisung kompiliert, aber bei Bewegungen vom Co-Prozessor wird die gesamte ARM -Pipeline angehalten. Der Aufwand ist recht hoch und kann die Messung verzerren. Glücklicherweise ist auch der Overhead fixiert, sodass Sie ihn messen und von Ihren Timings subtrahieren können. 

In meinem Beispiel habe ich das für jede Messung gemacht. Tun Sie das nicht in der Praxis. Ein Interrupt wird früher oder später zwischen den beiden Aufrufen auftreten und Ihre Messungen noch weiter verzerren. Ich schlage vor, dass Sie den Overhead einige Male in einem Leerlaufsystem messen, alle Außenseiter ignorieren und stattdessen eine feste Konstante verwenden.

45

Um auf die Antwort von Nils einzugehen, jetzt sind einige Jahre vergangen! - Eine einfache Möglichkeit, auf diese Zähler zuzugreifen, ist Kernel mit gator erstellen . Daraufhin werden Zählerwerte für die Verwendung mit Streamline gemeldet, dem Leistungsanalysetool von ARM.

Jede Funktion wird auf einer Timeline angezeigt (so erhalten Sie einen Überblick über die Leistung Ihres Systems) und zeigt Ihnen genau, wie lange die Ausführung ausgeführt hat, zusammen mit der prozentualen CPU-Auslastung. Sie können dies mit Diagrammen jedes Zählers vergleichen, den Sie für das Sammeln eingerichtet haben, und CPU-intensive Aufgaben bis auf Quellcodeebene ausführen.

Streamline funktioniert mit allen Prozessoren der Cortex-A-Serie.

1
Badmanton Casio

Sie müssen Ihren Code vor und nach Ihren Optimierungen mit Leistungsanalysetools profilieren.

Acct ist eine Befehlszeile und eine Funktion, mit der Sie Ihre Ressourcen überwachen können. Sie können über die Verwendung und Anzeige der Datendatei, die von acct generiert wird, weitere Informationen erhalten.

Ich werde diesen Beitrag mit anderen Tools zur Leistungsanalyse von Open Source aktualisieren.

Gprof ist ein anderes solches Werkzeug. Bitte überprüfen Sie die Dokumentation.

1
Praveen S

Ich habe in einer Toolchain für ARM7 gearbeitet, die einen Simulator auf Anweisungsebene hatte. Das Ausführen von Apps kann Zeitangaben für einzelne Zeilen und/oder Anweisungen geben. Das war großartig für eine Mikrooptimierung einer bestimmten Routine. Dieser Ansatz ist jedoch wahrscheinlich nicht für eine ganze App/ganze Systemoptimierung geeignet.

0
Digikata