Hallo zusammen, ich habe eine Funktion geschrieben (Quadratwurzel), deren Geschwindigkeit ich profilen wollte. Die Funktion ist im großen und Ganzen ein Lookup-Table für den Startwert und dann ein Bisektionsverfahren. Also alles reine Integer-Operationen. Das Profiling mache in natürlich mit vielen unterschiedlichen Werten. Am längsten braucht die Funktion für Werte knapp unter INT32_MAX. Jetzt kommt der (für mich) überraschende Teil: Am STM32F103 bei 72 MHz messe ich max. ca. 7 µs durch Pinwackeln am Oszilloskop und aus dem DWT_CYCCNT lese ich eine Differenz von 597 Zyklen. Am STM32F446 bei 168 MHz messe ich max. ca. 2,5 µs/393 Zyklen. Im Groben und Ganzen passen die gemessenen Zeiten am Oszilloskop und per Taktzähler zusammen. Was mich allerdings wundert: Warum benötigt der M4F für reine Integer-Aufgaben weniger Rechentakte als der M3? Ist die Multiplikation wesentlich schneller (Divisionen kommen nur durch Zwei vor), oder wo liegt der Unterschied?
Ich tippe auf den I-Cache im Flash-Interface (ART accelerator).
Das kann mehrere Ursachen haben. Wenn der Code aus dem Flash läuft, könnten die wait states für Zugriffe des flash eine Rolle spielen. Welche Compiler optionen nutzt Du für die unterschiedlichen Prozessoren? Hatte mal einen Compiler für den 103er, der eine spezielle Option brauchte um 32bit*32bit->64bit ergebnis Multiplikation zu optimieren. Vielleicht nutzt der Compiler auch den DSP Befehlssatz des M4F. Sieht man im generierten Assemblercode.
Gast
#5795236
Walter T. schrieb: > Jetzt kommt der (für mich) überraschende Teil: Am STM32F103 bei 72 MHz > messe ich max. ca. 7 µs durch Pinwackeln am Oszilloskop und aus dem > DWT_CYCCNT lese ich eine Differenz von 597 Zyklen. Am STM32F446 bei 168 > MHz messe ich max. ca. 2,5 µs/393 Zyklen. Bus- und Flash-Taktung in die Überlegung mit einbezogen? Sicher, dass keine Floating Point Operationen (aus versehen) enthalten sind (=wäre emuliert auf M3 über Library calls und M4F per FPU)? Sind Caches vorhanden? M4F hat einige SIMD Instruktionen; am besten per objdump ein Disassembly erzeugen und den generierten Code vergleichen.
Martin B. schrieb: > Hatte mal einen Compiler für den 103er, der eine spezielle Option > brauchte um 32bit*32bit->64bit ergebnis Multiplikation zu optimieren. 64-Bit-Multiplikationen kommen nicht vor. Der Compiler interessiert mich aber trotzdem. Welcher war es? x^y schrieb: > icher, dass > keine Floating Point Operationen (aus versehen) enthalten sind Da bin ich mir ziemlich sicher. Der Quelltext ist kein Geheimnis. Es wundert mich sowieso, dass Integer-Wurzel nicht in der Standard-Integer-Library vorhanden ist oder einfach zu bekommen ist, sondern selbst gebastelt werden muss. Weil mir der Vergleich fehlt, habe ich noch nicht einmal eine Vorstellung davon, ob meine Bastel-Wurzel von der Rechenzeit ganz OK oder schlecht ist.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | |
68 | |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | |
76 | |
77 | |
78 | |
79 | |
80 | |
81 | |
82 | |
83 | |
84 | |
85 | |
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 | |
93 | |
94 | |
95 | |
96 | |
97 | |
98 | |
99 | |
100 | |
101 | |
102 | |
103 | |
104 | |
105 | |
106 | |
107 | |
108 | |
109 | |
110 | |
111 | |
112 | |
113 | |
114 | |
115 | |
116 | |
117 | |
118 | |
119 | |
120 | |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | |
127 | |
128 | |
129 | |
130 | |
131 | |
132 | |
133 | |
134 | |
135 | |
136 | |
137 | |
138 | |
139 | |
140 | |
141 | |
142 | |
143 | |
144 | |
145 | |
146 | |
147 | |
148 | |
149 | |
x^y schrieb: > Sind Caches vorhanden? Ja, aber die sprechen eher für den langsamer getakteten M3, weil der, nach meinem Verständnis, weniger Flash-Wait-Cycles benötigt. x^y schrieb: > am besten per objdump ein > Disassembly erzeugen und den generierten Code vergleichen. Das mache ich dann heute abend mal.
Walter T. schrieb: >> Sind Caches vorhanden? > > Ja, aber die sprechen eher für den langsamer getakteten M3, weil der, > nach meinem Verständnis, weniger Flash-Wait-Cycles benötigt. Der STM32F103 hat einen kleinen Prefetch-Buffer, aber keine Caches. Folge: Ausgeführte Sprünge leiden voll unter den Waitstates, Datenzugriffe auf Flash ebenfalls. Der STM32F446 hat vor dem Flash einen 1kB Instruction Cache, um Waitstates bei ausgeführten Sprüngen abzufedern, und einen 128 Byte Data Cache für Zugriff auf dort gespeicherte Konstanten. Bei Code, dessen Sprungziele im tempo-relevanten Teil des Codes in diesen I-Cache passen, spielen die Waitstates beim F4 also keine Rolle, beim F1 jedoch schon. Datenzugriffe auf Literal Pools im Flash sollten bei Optimierung auf Tempo (das hast du hoffentlich gemacht) deutlich weniger auftreten als bei Optimierung auf Grösse, somit nicht von grosser Bedeutung sein.
Wer auch immer in der Zukunft über Google hierhinfindet: Den obigen Quelltext bitte NICHT verwenden. Da ist ein Bug drin. Wie dem auch sei: Der hier im Anhang ist sogar noch ein paar Takte schneller. Die gemessenen Laufzeiten für große und kleine Werte (Debug-Modus, -O1) habe ich mal tabellarisch zusammengefasst. clz() spart auch noch einmal ein paar Takte. Keine Ahnung, ob die Geschwindigkeit gut ist, aber für mich ist sie ausreichend.
Nur der Vollständigkeit halber
Gast
#5796559
Hast du schon einmal das Heron Verfahren probiert?
Der hier beschriebe binäre Algorithmus ist m.E. der einfachste und schnellste: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_(base_2) Sollte in einer optimierten Implementierung für ein 32 bit integer eigentlich deutlich unter 200 takte kommen... Das ist auch die top-antwort in dem oben verlinkten Stackoverflow thread.
Gast
#5796611
Muss es eigentlich eine Integer Wurzel sein? Der M4 kann 32bit-Float-Wurzel in 14 Takten: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439b/BEHJADED.html
Dr. Sommer schrieb: > Muss es eigentlich eine Integer Wurzel sein? Der M4 kann > 32bit-Float-Wurzel in 14 Takten: Ja, und das bis volle 31 Bit. Aber stimmt schon: Die Wurzel einfach in zwei float zerlegen, multiplizieren und dann trunkieren ist auf dem M4F immer noch schneller als das da oben. Da habe ich mich wohl in eine falsche Richtung verrannt.
Walter T. schrieb: > Ja, und das bis volle 31 Bit. 32 Bit Fließkommazahlen haben 24 Bit Genauigkeit. D.h. bei 31 bit ist der Abstand zwischen zwei darstellbaren Zahlen maximal 128. Daraus resultiert bei der Quadratwurzel maximal ein Fehler von 1, falls beim Runden eine Quadratzahl übersprungen wird (die Abstände der Quadratzahlen sind wesentlich größer). Das Ergebnis sollte dann für die dargestellte Zahl genau sein, und weil es nur maximal 16 Bit hat, ist es auch darstellbar.
Gast
#5796924
Walter T. schrieb: > Die gemessenen Laufzeiten für große und kleine Werte (Debug-Modus, -O1) > habe ich mal tabellarisch zusammengefasst. clz() spart auch noch einmal > ein paar Takte. Man darf die Performance von Algorithmen nicht im Debug-Modus vergleichen.
Aua schrieb: > Man darf die Performance von Algorithmen nicht im Debug-Modus > vergleichen. Na, dann hoffe ich einfach mal, daß mich keiner erwischt.
Gast
#5797138
Walter T. schrieb: > Na, dann hoffe ich einfach mal, daß mich keiner erwischt. Naja, es bringt halt nichts. Die Ergebnisse können mit vs. ohne Optimierung dramatisch abweichen. Was nützt es dir, einen Algorithmus auszuwählen, welcher nur ohne Optimierung schneller ist? Dazu kommt, dass gut lesbare Schreibweisen ohne Optimierung ggf. langsamer sind und man sich so schwierig wartbaren Code baut.
Dr. Sommer schrieb: > Naja, es bringt halt nichts. Im Gegenteil. Ich weiß dann, daß der Code auch schon im Debug-Modus schnell genug ist. Eigentlich geht es doch (mir) meistens darum, nicht den letzten Takt herauszuquetschen, sondern nur darum, zu wissen, ob der jeweilige Ansatz schnell genug ist, oder ob man eine andere Lösung finden muß.
Gast
#5797161
Walter T. schrieb: > Im Gegenteil. Ich weiß dann, daß der Code auch schon im Debug-Modus > schnell genug ist. Auch auf die Gefahr hin, einen Mikro-Optimierten Algorithmus zu wählen, der so clever ist dass er auch ohne Optimizer geht, während man auch einen lesbaren wartbaren Algorithmus hätte wählen können, der mit Optimizer mindestens genau so schnell, ohne Optimizer aber zu langsam wäre? Schau dir den unoptimierten Assemblercode mal an. Der ist wirklich furchtbar naiv. Damit der schnell genug ist, muss man im C-Code ggf. diverse Verrenkungen machen.
Jürgen S. schrieb: > Das Ergebnis sollte dann für die dargestellte Zahl genau sein, und weil > es nur maximal 16 Bit hat, ist es auch darstellbar. Da der Zahlenbereich von 0 bis 2147483648 ja noch überschaubar ist, habe ich einfach mal mit vollständiger Enumeration nachgeschaut. Bis 16785406 ist sqrtf() (erwartungsgemäß) korrekt, darüber beträgt der Fehler maximal ±1 in beide Richtungen. Damit kann ich leben.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.

