mikrocontroller.net

Forum: Compiler & IDEs Cortex M4 interne Clock zum Cycles messen nutzen


Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo allerseits,

ich habe auf Stackexchange diesen Vorgang gefunden. [1] "Number of 
Cycles"

Zunächst die erste Frage an die Profis: Macht der Kram exakt das, was er 
machen soll?


Nun zu meiner Beobachtung: Führe ich einfach folgendes aus:
reset_timer();
start_timer();
stop_timer();

und lasse mir das Ergebnis von getCycles() ausgeben, so erhalte ich die 
Zahl "35". Führe ich eine Multiplikation zweier 32-Bit Zahlen aus, 
erhalte ich ebenfalls 35.
Selbst bei 40k rekursiven Additionen wird mir der Wert 35 ausgegeben.

Führe ich eine selbstprogrammierte Operation (GF(p^16) Multiplikation) 
durch, erhalte ich einen von 35 abweichenden Wert. Aber die Anfängliche 
Ausgabe irritiert mich sehr und daher stelle ich Zweifel an diese 
Benchmarkingvorgehensweise.


Mein Board ist übrigens das Cortex M4: STM32F407VGT6 auf einem CJMCU 
Board.

[1] 
https://stackoverflow.com/questions/32610019/arm-m...



Edit: Vlt. hätte ich mir mal den Code genauer anschauen müssen. Dort 
wird ein Bit durch eine "Oder" Operation aktiviert.. das kann nicht 
klappen :D

Vorschlag:
void start_timer(){
    *DWT_CONTROL = *DWT_CONTROL | 1 ; // enable the counter
}

void stop_timer(){
    *DWT_CONTROL = *DWT_CONTROL & 0xFFFFFFFE ; // disable the counter    
}

und
void reset_timer(){
    DWT_CYCCNT   = (int *)0xE0001004; //address of the register
    DWT_CONTROL  = (int *)0xE0001000; //address of the register
    SCB_DEMCR    = (int *)0xE000EDFC; //address of the register
    *SCB_DEMCR   = *SCB_DEMCR | 0x01000000;
    *DWT_CYCCNT  = 0; // reset the counter
    *DWT_CONTROL = 0; 
}


Diese "Vorschläge" führen nun dazu, dass eine Ausgabe ohne Operationen 
"19" und eine Multiplikation "uint32_t c = 0xFF*0xFFFF" 18 "cycles" 
braucht. Da stimmt also immer noch was nicht ;)

: Bearbeitet durch User
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja das funktioniert einwandfrei. Ich hab zwar mittlerweile eine Klasse 
dafür, aber das C-äquivalent wär in etwa das hier:
#define CORE_DWT_CYCCNT_EN()    *((volatile uint32_t*)0xE0001000) = 0x40000001  //!< Enable CYCCNT register
#define CORE_DWT_CYCCNT_DIS()   *((volatile uint32_t*)0xE0001000) = 0x40000000  //!< Disable CYCCNT register
#define CORE_DWT_CYCCNT_GET()   *((volatile uint32_t*)0xE0001004)               //!< Get value from CYCCNT register


Und folgendermaßen zu nutzen:
uint32_t it1, it2;                                              
float f = 1.01f;                                                
CORE_DWT_CYCCNT_EN();                                          
it1 = CORE_DWT_CYCCNT_GET();
float f2 = f * 2.29f;
it2 = CORE_DWT_CYCCNT_GET() - it1;                             
CORE_DWT_CYCCNT_DIS();

Laut meinen Notizen braucht die Subtraktion und der Store nach "it2" 
selbst 6 Zyklen... aber das weiß ich grad ehrlich gesagt nicht 
auswendig, also bitte selbst nachschaun.

Autor: Ruediger A. (Firma: keine) (rac)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Doch doch, die DWT funktioniert. Allerdings kann es sein, dass dein 
Debugger sie mit nutzt, also asynchron auf 0 setzt.

Du brauchst auch kein stop_timer(), sondern kannst jederzeit den cycle 
count auslesen. Mglw. setzt das Stoppen implizit den Counter zurück, und 
die 35 Zyklen sind genau das Delta zwischen stop_timer() und 
get_cycles(). Ich würde auch die Abstraktionen nicht nutzen, sondern die 
Registeroperation auf die DWT inline im Code machen, weil Du sonst immer 
um die Zyklen verfälscht bist, die der Aufruf in die Unterfunktion 
verbrät.

Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Einen Debugger habe ich nicht angeschlossen. Mein Programmaufbau ist so:
reset_timer();
start_timer();
stop_timer();
print_int(getCycles());

print_int() sendet über USART einen String, der den Count enthält. 
Ausgelesen wird in Ubuntu im Terminal. Ausgabe ist an dieser Stelle 19.
reset_timer();
start_timer();
stop_timer();
uint32_t c= 0xFF*0xFFFF;
print_int(getCycles());
Output: 18

Autor: RAc (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
was willst Du damit messen? Du stoppst den Timer, bevor Du die 
Multiplikation machst?

Autor: Peter D. (peda)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Christopher S. schrieb:
> stop_timer();
> uint32_t c= 0xFF*0xFFFF;

Compiler sind manchmal komisch, sie führen Sachen in der Reihenfolge 
aus, wie man sie hinschreibt.
Und oft sind sie faul, sie rechnen Konstanten aus und schreiben das 
Ergebnis direkt hin.

Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ehm.. Hab es falsch zusammenkopiert. Tatsächlich wird erst multipliziert 
und dann gestoppt. Sry, ich hätte etwas aufmerksamer sein sollen.

Ich habe übrigens aktuell die Version mit meinen Änderungen aktiv. Waren 
die Änderungen sinnvoll? Ich habe praktisch das |1 gegen & 0xFFFFFFFE 
getauscht. Auch gucke ich grade nach, ob die Adressen alle korrekt für 
den M4 sind.

Autor: Markus F. (mfro)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Christopher S. schrieb:
> Tatsächlich wird erst multipliziert

Zumindest in deinem Beispiel wird überhaupt nix multipliziert (s. zwei 
Beiträge weiter oben).

Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ah, dann berechnet der Compiler den Kram vorher? Müsste ich dann a=0xFF, 
b=0xFFFF; und dann entsprechend c=a*b; berechnen? Oder wie sieht das 
dann aus? Theoretisch sind ja a*b dann während der Compilierung bekannt.

Gut, vielen Dank für die Hilfe. Den Rest werde ich mir jetzt wohl 
anlesen können :)

: Bearbeitet durch User
Autor: Markus F. (mfro)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
Der Compiler erkennt, daß sich an der Multiplikation nie was ändert, 
berechnet das Ergebnis entsprechend bereits beim Compilieren und 
schreibt eine Konstante.

Anschliessend erkennt er, daß sich niemand für diese Konstante 
interessiert (sie wird ja nirgends verwendet) und optimiert sie deshalb 
gleich komplett weg.

Um tatsächlich etwas zu berechnen, musst Du schon noch etwas kreativer 
werden ;)

Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn man davon ausgeht, dass zumindest der cmsis-core_cm4.h mit 
eingebunden wird, geht das alles auch noch ein wenig schöner.
void init_debug_counter(void) {
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
}

void start_debug_counter(void) {
  DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

void end_debug_counter(void) {
  DWT->CTRL &= ~(DWT_CTRL_CYCCNTENA_Msk);
}

uint32_t get_debug_counter(void) {
  return DWT->CYCCNT;
}

Das ganze kann man ggf. noch in Macros verpacken.

Autor: Root (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Nico W. schrieb:
> Das ganze kann man ggf. noch in Macros verpacken

function like macro = inline
inline = schöner

Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vincent H. schrieb:
> Ja das funktioniert einwandfrei. Ich hab zwar mittlerweile eine Klasse
> dafür, aber das C-äquivalent wär in etwa das hier:
>
> #define CORE_DWT_CYCCNT_EN()    *((volatile uint32_t*)0xE0001000) = 
> 0x40000001  //!< Enable CYCCNT register
> #define CORE_DWT_CYCCNT_DIS()   *((volatile uint32_t*)0xE0001000) = 
> 0x40000000  //!< Disable CYCCNT register
> #define CORE_DWT_CYCCNT_GET()   *((volatile uint32_t*)0xE0001004) 
> //!< Get value from CYCCNT register
> 
>

Ich habe diesen Codeschnipsel übernommen und angepasst. Der 
Reset-Counter scheint leider nicht zu klappen. Theoretisch müsste ich 
diesen doch als
#define CORE_DWT_CYCCNT_RESET() *((volatile uint32_t*)0xE0001004) = 0

resetten können. Jedoch habe ich teilweise solch enorme Ausreißer, dass 
es scheint, als würde dies nicht funktionieren. Als Beispiel:

 // Arithmetisches Mittel bei 1k Messungen
Round 1: 1429992.875000
Round 2: 1280387.375000
Round 3: 18446744949882880.000000

Nach jeder Runde verwende ich den "reset". Hat dafür jemand einen Rat 
oder sieht meinen Fehler?

Viele Grüße und vielen Dank für die Hilfen.

Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dann zeige doch mal den Code der nicht funktioniert und nicht den der 
funktioniert.


Wie wird denn dein arithmetisches Mittel berechnet?!?


Ich würde das auch nicht immer wieder reseten wenn es geht sondern 
zwischen Anfang und Ende die Differenz bilden. Das geht mit unsigned 
sehr einfach.

: Bearbeitet durch User
Autor: Marc (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich würde das Reseten auch bleiben lassen.
Stell Dir vor Du nutzt den Counter in mehreren Threads/Interrupts.
Dann machst Du Dir die Zeitmessungen kaputt.

Starte ihn einmalig beim Init und lass ihn durchlaufen.

Autor: Christopher S. (shalec)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich verwende den Counter so:

[c]
#define TESTS = 1000
#define ROUNDS = 10

int j;
uint64_t c1, all1=0;

for(int i=0; i<ROUNDS; i++){
    j=TESTS;
    start_counter();
    while(j--){
        c1 = get_timer();
        //Funktionsaufruf
        all1 += get_timer() - c1;
    }
    stop_counter();
    //reset_counter();
    printf("%llu", all1/TESTS);
}

Hier kommen halt die Ausreißer vor. Auch jetzt habe ich einen Durchlauf 
über Nacht mit einer sehr aufwändigen Funktion gemacht und die Werte 
variieren zwischen 2m und 60m. Solch gewaltige Abweichungen finde ich 
heftig und kann sie mir nur mit einem vollem Counter erklären.

Übrigens haben die oben erwähnten Ausreißer (Round 3) genau so lange 
gebraucht, wie die anderen Messungen auch.


Sollte ich den Start und Stop in die Whileschleife legen?

Der punkt mit den mehreren Threads wäre überlegenswert, hat der Cortex 
M4 jedoch nicht. Auch nutze ich keine Interrupts. Ich möchte damit ja 
nur den gesamten Aufwand bemessen und das relativ zuverlässig.

: Bearbeitet durch User
Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ist ne blöde Idee mit überläuften zu rechnen und dann den falschen Typen 
auszuwählen.

Was passiert wenn get_timer() < c1 ist?

get_timer() ist 32bit. Ganz einfaches Beispiel:

get_timer() = 0
c1 = 1

0 - 1 = 18446744073709551615

Mach aus c1 einen uint32_t und alles sollte klappen.

Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Und? Hattest du Erfolg?

Hier noch ein Beispiel.
    while(j--){
        c1 = get_timer(); //z.B. 4.293.700.000 kurz vorm überlauf
        //Funktionsaufruf // dauer 1.430.000
                          // get_timer() == 162.704
        all1 += get_timer() - c1; 
                          // 162.704 - 4.293.700.000
                          // c1 64bit: 18.446.744.069.416.014.320
                          // c1 32bit: 1.430.000
    }

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.