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 ;)
#define CORE_DWT_CYCCNT_GET() *((volatile uint32_t*)0xE0001004) //!< Get value from CYCCNT register
Und folgendermaßen zu nutzen:
1
uint32_tit1,it2;
2
floatf=1.01f;
3
CORE_DWT_CYCCNT_EN();
4
it1=CORE_DWT_CYCCNT_GET();
5
floatf2=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.
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.
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.
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.
Christopher S. schrieb:> Tatsächlich wird erst multipliziert
Zumindest in deinem Beispiel wird überhaupt nix multipliziert (s. zwei
Beiträge weiter oben).
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 :)
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 ;)
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.
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.
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.
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.