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


von Christopher S. (shalec)


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:
1
reset_timer();
2
start_timer();
3
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-m4-instructions-per-cycle-ipc-counters



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:
1
void start_timer(){
2
    *DWT_CONTROL = *DWT_CONTROL | 1 ; // enable the counter
3
}
4
5
void stop_timer(){
6
    *DWT_CONTROL = *DWT_CONTROL & 0xFFFFFFFE ; // disable the counter    
7
}

und
1
void reset_timer(){
2
    DWT_CYCCNT   = (int *)0xE0001004; //address of the register
3
    DWT_CONTROL  = (int *)0xE0001000; //address of the register
4
    SCB_DEMCR    = (int *)0xE000EDFC; //address of the register
5
    *SCB_DEMCR   = *SCB_DEMCR | 0x01000000;
6
    *DWT_CYCCNT  = 0; // reset the counter
7
    *DWT_CONTROL = 0; 
8
}


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
von Vincent H. (vinci)


Lesenswert?

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


Und folgendermaßen zu nutzen:
1
uint32_t it1, it2;                                              
2
float f = 1.01f;                                                
3
CORE_DWT_CYCCNT_EN();                                          
4
it1 = CORE_DWT_CYCCNT_GET();
5
float f2 = f * 2.29f;
6
it2 = CORE_DWT_CYCCNT_GET() - it1;                             
7
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.

von Ruediger A. (Firma: keine) (rac)


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.

von Christopher S. (shalec)


Lesenswert?

Einen Debugger habe ich nicht angeschlossen. Mein Programmaufbau ist so:
1
reset_timer();
2
start_timer();
3
stop_timer();
4
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.
1
reset_timer();
2
start_timer();
3
stop_timer();
4
uint32_t c= 0xFF*0xFFFF;
5
print_int(getCycles());
Output: 18

von RAc (Gast)


Lesenswert?

was willst Du damit messen? Du stoppst den Timer, bevor Du die 
Multiplikation machst?

von Peter D. (peda)


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.

von Christopher S. (shalec)


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.

von Markus F. (mfro)


Lesenswert?

Christopher S. schrieb:
> Tatsächlich wird erst multipliziert

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

von Christopher S. (shalec)


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
von Markus F. (mfro)


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

von Nico W. (nico_w)


Lesenswert?

Wenn man davon ausgeht, dass zumindest der cmsis-core_cm4.h mit 
eingebunden wird, geht das alles auch noch ein wenig schöner.
1
void init_debug_counter(void) {
2
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
3
}
4
5
void start_debug_counter(void) {
6
  DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
7
}
8
9
void end_debug_counter(void) {
10
  DWT->CTRL &= ~(DWT_CTRL_CYCCNTENA_Msk);
11
}
12
13
uint32_t get_debug_counter(void) {
14
  return DWT->CYCCNT;
15
}

Das ganze kann man ggf. noch in Macros verpacken.

von Root (Gast)


Lesenswert?

Nico W. schrieb:
> Das ganze kann man ggf. noch in Macros verpacken

function like macro = inline
inline = schöner

von Christopher S. (shalec)


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:
>
1
> #define CORE_DWT_CYCCNT_EN()    *((volatile uint32_t*)0xE0001000) = 
2
> 0x40000001  //!< Enable CYCCNT register
3
> #define CORE_DWT_CYCCNT_DIS()   *((volatile uint32_t*)0xE0001000) = 
4
> 0x40000000  //!< Disable CYCCNT register
5
> #define CORE_DWT_CYCCNT_GET()   *((volatile uint32_t*)0xE0001004) 
6
> //!< Get value from CYCCNT register
7
>
>

Ich habe diesen Codeschnipsel übernommen und angepasst. Der 
Reset-Counter scheint leider nicht zu klappen. Theoretisch müsste ich 
diesen doch als
1
#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:

1
 // Arithmetisches Mittel bei 1k Messungen
2
Round 1: 1429992.875000
3
Round 2: 1280387.375000
4
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.

von Nico W. (nico_w)


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
von Marc (Gast)


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.

von Christopher S. (shalec)


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
von Nico W. (nico_w)


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.

von Nico W. (nico_w)


Lesenswert?

Und? Hattest du Erfolg?

Hier noch ein Beispiel.
1
    while(j--){
2
        c1 = get_timer(); //z.B. 4.293.700.000 kurz vorm überlauf
3
        //Funktionsaufruf // dauer 1.430.000
4
                          // get_timer() == 162.704
5
        all1 += get_timer() - c1; 
6
                          // 162.704 - 4.293.700.000
7
                          // c1 64bit: 18.446.744.069.416.014.320
8
                          // c1 32bit: 1.430.000
9
    }

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.